├── log
└── .keep
├── tmp
└── .keep
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── public
├── favicon.ico
├── apple-touch-icon.png
├── apple-touch-icon-precomposed.png
├── robots.txt
├── 500.html
├── 422.html
└── 404.html
├── test
├── helpers
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── user_test.rb
│ ├── charge_test.rb
│ ├── courses_test.rb
│ └── stripe_account_test.rb
├── controllers
│ ├── .keep
│ ├── disputes_controller_test.rb
│ ├── payouts_controller_test.rb
│ ├── debit_cards_controller_test.rb
│ ├── charges_controller_test.rb
│ ├── campaigns_controller_test.rb
│ ├── bank_accounts_controller_test.rb
│ └── stripe_accounts_controller_test.rb
├── fixtures
│ ├── .keep
│ ├── files
│ │ └── .keep
│ ├── campaigns.yml
│ ├── charges.yml
│ ├── stripe_accounts.yml
│ └── users.yml
├── integration
│ └── .keep
└── test_helper.rb
├── .ruby-version
├── app
├── assets
│ ├── images
│ │ ├── .keep
│ │ ├── sf_sm.jpg
│ │ ├── desk_lg.jpg
│ │ ├── desk_sm.jpg
│ │ ├── favicon.ico
│ │ ├── sf_lg.jpeg
│ │ ├── snow_sm.jpg
│ │ ├── beach_lg.jpeg
│ │ ├── beach_sm.jpg
│ │ ├── money_lg.jpg
│ │ ├── money_sm.jpg
│ │ ├── ocean_lg.jpg
│ │ ├── ocean_sm.jpeg
│ │ ├── puppy_lg.jpeg
│ │ ├── puppy_sm.jpg
│ │ ├── snow_lg.jpeg
│ │ ├── travel_lg.jpeg
│ │ ├── travel_sm.jpg
│ │ ├── wedding_sm.jpg
│ │ └── wedding_lg.jpeg
│ ├── javascripts
│ │ ├── channels
│ │ │ └── .keep
│ │ ├── campaigns.js
│ │ ├── cable.js
│ │ └── application.js
│ ├── config
│ │ └── manifest.js
│ └── stylesheets
│ │ ├── application.css
│ │ ├── components.scss
│ │ └── custom.scss
├── models
│ ├── concerns
│ │ └── .keep
│ ├── charge.rb
│ ├── application_record.rb
│ ├── user.rb
│ ├── campaign.rb
│ └── stripe_account.rb
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── pages_controller.rb
│ ├── registrations_controller.rb
│ ├── application_controller.rb
│ ├── payouts_controller.rb
│ ├── disputes_controller.rb
│ ├── bank_accounts_controller.rb
│ ├── debit_cards_controller.rb
│ ├── webhooks_controller.rb
│ ├── charges_controller.rb
│ ├── campaigns_controller.rb
│ └── stripe_accounts_controller.rb
├── views
│ ├── layouts
│ │ ├── mailer.text.erb
│ │ ├── mailer.html.erb
│ │ ├── _messages.html.erb
│ │ ├── _footer.html.erb
│ │ ├── application.html.erb
│ │ └── _header.html.erb
│ ├── kaminari
│ │ ├── _gap.html.erb
│ │ ├── _first_page.html.erb
│ │ ├── _last_page.html.erb
│ │ ├── _next_page.html.erb
│ │ ├── _prev_page.html.erb
│ │ ├── _page.html.erb
│ │ └── _paginator.html.erb
│ ├── devise
│ │ ├── mailer
│ │ │ ├── password_change.html.erb
│ │ │ ├── confirmation_instructions.html.erb
│ │ │ ├── unlock_instructions.html.erb
│ │ │ └── reset_password_instructions.html.erb
│ │ ├── unlocks
│ │ │ └── new.html.erb
│ │ ├── confirmations
│ │ │ └── new.html.erb
│ │ ├── passwords
│ │ │ ├── edit.html.erb
│ │ │ └── new.html.erb
│ │ ├── shared
│ │ │ └── _links.html.erb
│ │ ├── sessions
│ │ │ └── new.html.erb
│ │ └── registrations
│ │ │ ├── new.html.erb
│ │ │ └── edit.html.erb
│ ├── admin
│ │ └── kaminari
│ │ │ ├── _gap.html.erb
│ │ │ ├── _last_page.html.erb
│ │ │ ├── _first_page.html.erb
│ │ │ ├── _next_page.html.erb
│ │ │ ├── _prev_page.html.erb
│ │ │ ├── _page.html.erb
│ │ │ └── _paginator.html.erb
│ ├── stripe_accounts
│ │ ├── new.html.erb
│ │ ├── edit.html.erb
│ │ └── _account_form.html.erb
│ ├── campaigns
│ │ ├── _verification.html.erb
│ │ ├── _donations.html.erb
│ │ ├── _transactions.html.erb
│ │ ├── _checkoutform.html.erb
│ │ ├── _campaigns.html.erb
│ │ ├── _transfers.html.erb
│ │ ├── _paymentform.html.erb
│ │ ├── edit.html.erb
│ │ ├── home.html.erb
│ │ ├── _images.html.erb
│ │ ├── new.html.erb
│ │ ├── dashboard.html.erb
│ │ ├── _charges.html.erb
│ │ └── show.html.erb
│ ├── pages
│ │ ├── terms.html.erb
│ │ └── pricing.html.erb
│ ├── debit_cards
│ │ ├── _debit_card_form.html.erb
│ │ └── new.html.erb
│ ├── bank_accounts
│ │ ├── _bank_account_form.html.erb
│ │ └── new.html.erb
│ ├── payouts
│ │ └── show.html.erb
│ └── charges
│ │ ├── _dispute.html.erb
│ │ └── show.html.erb
├── helpers
│ ├── charges_helper.rb
│ ├── payouts_helper.rb
│ ├── disputes_helper.rb
│ ├── bank_accounts_helper.rb
│ ├── debit_cards_helper.rb
│ ├── application_helper.rb
│ ├── devise_helper.rb
│ └── stripe_accounts_helper.rb
├── jobs
│ └── application_job.rb
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
└── mailers
│ └── application_mailer.rb
├── vendor
└── assets
│ ├── javascripts
│ └── .keep
│ └── stylesheets
│ └── .keep
├── bin
├── bundle
├── rake
├── rails
├── spring
├── update
└── setup
├── config
├── spring.rb
├── boot.rb
├── environment.rb
├── cable.yml
├── initializers
│ ├── session_store.rb
│ ├── mime_types.rb
│ ├── stripe.rb
│ ├── application_controller_renderer.rb
│ ├── filter_parameter_logging.rb
│ ├── cookies_serializer.rb
│ ├── kaminari_config.rb
│ ├── backtrace_silencers.rb
│ ├── assets.rb
│ ├── wrap_parameters.rb
│ ├── inflections.rb
│ └── new_framework_defaults.rb
├── application.rb
├── database.yml
├── locales
│ ├── en.yml
│ └── devise.en.yml
├── routes.rb
├── secrets.yml
├── environments
│ ├── test.rb
│ ├── development.rb
│ └── production.rb
└── puma.rb
├── config.ru
├── db
├── migrate
│ ├── 20160911175135_add_image_to_course.rb
│ ├── 20161228180341_add_name_to_charges.rb
│ ├── 20160910213144_rename_lessons_to_courses.rb
│ ├── 20161227183231_add_goal_to_campaigns.rb
│ ├── 20161228182556_add_raised_to_campaign.rb
│ ├── 20161227175740_rename_courses_to_campaigns.rb
│ ├── 20160911185541_add_stripe_account_to_users.rb
│ ├── 20160915051609_add_content_link_to_courses.rb
│ ├── 20161228174458_remove_user_id_from_charges.rb
│ ├── 20160915051353_change_description_in_courses.rb
│ ├── 20161228223505_add_active_to_campaign.rb
│ ├── 20170502170520_add_acct_id_to_stripe_accounts.rb
│ ├── 20161227180421_modify_campaigns_and_charges.rb
│ ├── 20160910162519_create_lessons.rb
│ ├── 20160913025720_create_charges.rb
│ ├── 20160911193440_create_stripe_accounts.rb
│ ├── 20160911230223_add_fields_to_stripe_account.rb
│ ├── 20170405165023_add_more_fields_to_stripe_accounts.rb
│ └── 20160910151553_devise_create_users.rb
├── seeds.rb
└── schema.rb
├── Rakefile
├── .gitignore
├── Gemfile
├── README.md
└── Gemfile.lock
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.4.5
2 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/helpers/charges_helper.rb:
--------------------------------------------------------------------------------
1 | module ChargesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/payouts_helper.rb:
--------------------------------------------------------------------------------
1 | module PayoutsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/disputes_helper.rb:
--------------------------------------------------------------------------------
1 | module DisputesHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/bank_accounts_helper.rb:
--------------------------------------------------------------------------------
1 | module BankAccountsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/debit_cards_helper.rb:
--------------------------------------------------------------------------------
1 | module DebitCardsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/models/charge.rb:
--------------------------------------------------------------------------------
1 | class Charge < ApplicationRecord
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/test/fixtures/campaigns.yml:
--------------------------------------------------------------------------------
1 | one:
2 | id: 1
3 | user_id: 1
4 | title: Title
5 | description: Description
--------------------------------------------------------------------------------
/app/controllers/pages_controller.rb:
--------------------------------------------------------------------------------
1 | class PagesController < ApplicationController
2 | def pricing
3 | end
4 | end
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/assets/images/sf_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/sf_sm.jpg
--------------------------------------------------------------------------------
/app/views/kaminari/_gap.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= content_tag :a, raw(t 'views.pagination.truncate') %>
3 |
4 |
--------------------------------------------------------------------------------
/app/assets/images/desk_lg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/desk_lg.jpg
--------------------------------------------------------------------------------
/app/assets/images/desk_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/desk_sm.jpg
--------------------------------------------------------------------------------
/app/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/favicon.ico
--------------------------------------------------------------------------------
/app/assets/images/sf_lg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/sf_lg.jpeg
--------------------------------------------------------------------------------
/app/assets/images/snow_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/snow_sm.jpg
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/app/assets/images/beach_lg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/beach_lg.jpeg
--------------------------------------------------------------------------------
/app/assets/images/beach_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/beach_sm.jpg
--------------------------------------------------------------------------------
/app/assets/images/money_lg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/money_lg.jpg
--------------------------------------------------------------------------------
/app/assets/images/money_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/money_sm.jpg
--------------------------------------------------------------------------------
/app/assets/images/ocean_lg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/ocean_lg.jpg
--------------------------------------------------------------------------------
/app/assets/images/ocean_sm.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/ocean_sm.jpeg
--------------------------------------------------------------------------------
/app/assets/images/puppy_lg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/puppy_lg.jpeg
--------------------------------------------------------------------------------
/app/assets/images/puppy_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/puppy_sm.jpg
--------------------------------------------------------------------------------
/app/assets/images/snow_lg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/snow_lg.jpeg
--------------------------------------------------------------------------------
/app/assets/images/travel_lg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/travel_lg.jpeg
--------------------------------------------------------------------------------
/app/assets/images/travel_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/travel_sm.jpg
--------------------------------------------------------------------------------
/app/assets/images/wedding_sm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/wedding_sm.jpg
--------------------------------------------------------------------------------
/app/assets/images/wedding_lg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamjstevenson/stripe-connect-managed-rails/HEAD/app/assets/images/wedding_lg.jpeg
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
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 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/test/controllers/disputes_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class DisputesControllerTest < ActionDispatch::IntegrationTest
4 |
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/kaminari/_first_page.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote %>
3 |
4 |
--------------------------------------------------------------------------------
/app/views/kaminari/_last_page.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} %>
3 |
4 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class UserTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/password_change.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | We're contacting you to notify you that your password has been changed.
4 |
--------------------------------------------------------------------------------
/app/views/kaminari/_next_page.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote %>
3 |
4 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 |
--------------------------------------------------------------------------------
/test/models/charge_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ChargeTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/courses_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class LessonTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/kaminari/_prev_page.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote %>
3 |
4 |
--------------------------------------------------------------------------------
/db/migrate/20160911175135_add_image_to_course.rb:
--------------------------------------------------------------------------------
1 | class AddImageToCourse < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :courses, :image, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20161228180341_add_name_to_charges.rb:
--------------------------------------------------------------------------------
1 | class AddNameToCharges < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :charges, :name, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20160910213144_rename_lessons_to_courses.rb:
--------------------------------------------------------------------------------
1 | class RenameLessonsToCourses < ActiveRecord::Migration[5.0]
2 | def change
3 | rename_table :lessons, :courses
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20161227183231_add_goal_to_campaigns.rb:
--------------------------------------------------------------------------------
1 | class AddGoalToCampaigns < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :campaigns, :goal, :integer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/test/models/stripe_account_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class StripeAccountTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_seller-dashboard_session'
4 |
--------------------------------------------------------------------------------
/db/migrate/20161228182556_add_raised_to_campaign.rb:
--------------------------------------------------------------------------------
1 | class AddRaisedToCampaign < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :campaigns, :raised, :integer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20161227175740_rename_courses_to_campaigns.rb:
--------------------------------------------------------------------------------
1 | class RenameCoursesToCampaigns < ActiveRecord::Migration[5.0]
2 | def change
3 | rename_table :courses, :campaigns
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/db/migrate/20160911185541_add_stripe_account_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddStripeAccountToUsers < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :users, :stripe_account, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20160915051609_add_content_link_to_courses.rb:
--------------------------------------------------------------------------------
1 | class AddContentLinkToCourses < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :courses, :content_link, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20161228174458_remove_user_id_from_charges.rb:
--------------------------------------------------------------------------------
1 | class RemoveUserIdFromCharges < ActiveRecord::Migration[5.0]
2 | def change
3 | remove_column :charges, :user_id, :integer
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20160915051353_change_description_in_courses.rb:
--------------------------------------------------------------------------------
1 | class ChangeDescriptionInCourses < ActiveRecord::Migration[5.0]
2 | def change
3 | change_column :courses, :description, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20161228223505_add_active_to_campaign.rb:
--------------------------------------------------------------------------------
1 | class AddActiveToCampaign < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :campaigns, :active, :boolean, default: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/controllers/registrations_controller.rb:
--------------------------------------------------------------------------------
1 | class RegistrationsController < Devise::RegistrationsController
2 | protected
3 |
4 | def after_sign_up_path_for(resource)
5 | new_campaign_path
6 | end
7 | end
--------------------------------------------------------------------------------
/db/migrate/20170502170520_add_acct_id_to_stripe_accounts.rb:
--------------------------------------------------------------------------------
1 | class AddAcctIdToStripeAccounts < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :stripe_accounts, :acct_id, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/stripe.rb:
--------------------------------------------------------------------------------
1 | Rails.configuration.stripe = {
2 | :publishable_key => ENV['PUBLISHABLE_KEY'],
3 | :secret_key => ENV['SECRET_KEY']
4 | }
5 |
6 | Stripe.api_key = Rails.configuration.stripe[:secret_key]
7 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ApplicationController.renderer.defaults.merge!(
4 | # http_host: 'example.org',
5 | # https: false
6 | # )
7 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.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 |
--------------------------------------------------------------------------------
/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]
5 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/app/assets/javascripts/campaigns.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | $(".campaign-image").click(function(){
3 | $(".img-thumbnail").removeClass("img-selected");
4 | $("#campaign_image").val(this.id);
5 | $(this).children("img").addClass("img-selected");
6 | });
7 | });
--------------------------------------------------------------------------------
/app/views/devise/mailer/confirmation_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Welcome <%= @email %>!
2 |
3 | You can confirm your account email through the link below:
4 |
5 | <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
6 |
--------------------------------------------------------------------------------
/test/controllers/payouts_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class PayoutsControllerTest < ActionDispatch::IntegrationTest
4 | test "should require valid payout ID" do
5 | get payout_path('fake')
6 | assert_response :redirect
7 | end
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | def format_amount(amount)
3 | sprintf('$%0.2f', amount.to_f / 100.0).gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
4 | end
5 |
6 | def format_date(created)
7 | Time.at(created).getutc.strftime("%m/%d/%Y")
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/db/migrate/20161227180421_modify_campaigns_and_charges.rb:
--------------------------------------------------------------------------------
1 | class ModifyCampaignsAndCharges < ActiveRecord::Migration[5.0]
2 | def change
3 | remove_column :campaigns, :content_link, :string
4 | remove_column :campaigns, :price, :integer
5 | rename_column :charges, :course_id, :campaign_id
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ApplicationRecord
2 | has_many :campaigns
3 | # Include default devise modules. Others available are:
4 | # :confirmable, :lockable, :timeoutable and :omniauthable
5 | devise :database_authenticatable, :registerable,
6 | :recoverable, :rememberable, :trackable, :validatable
7 | end
8 |
--------------------------------------------------------------------------------
/test/fixtures/charges.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | charge_id: MyString
5 | amount: 1
6 | amount_refunded: 1
7 | campaign_id: 1
8 |
9 | two:
10 | charge_id: MyString
11 | amount: 1
12 | amount_refunded: 1
13 | campaign_id: 1
14 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/unlock_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | Your account has been locked due to an excessive number of unsuccessful sign in attempts.
4 |
5 | Click the link below to unlock your account:
6 |
7 | <%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
8 |
--------------------------------------------------------------------------------
/db/migrate/20160910162519_create_lessons.rb:
--------------------------------------------------------------------------------
1 | class CreateLessons < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :lessons do |t|
4 | t.integer :user_id
5 | t.string :title
6 | t.string :description
7 | t.integer :price
8 | t.boolean :subscription
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/views/kaminari/_page.html.erb:
--------------------------------------------------------------------------------
1 | <% if page.current? %>
2 |
3 | <%= content_tag :a, page, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)) %>
4 |
5 | <% else %>
6 |
7 | <%= link_to page, url, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)) %>
8 |
9 | <% end %>
10 |
--------------------------------------------------------------------------------
/db/migrate/20160913025720_create_charges.rb:
--------------------------------------------------------------------------------
1 | class CreateCharges < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :charges do |t|
4 | t.string :charge_id
5 | t.integer :amount
6 | t.integer :amount_refunded
7 | t.integer :user_id
8 | t.integer :course_id
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/models/campaign.rb:
--------------------------------------------------------------------------------
1 | class Campaign < ApplicationRecord
2 | belongs_to :user
3 |
4 | validates :title,
5 | presence: true, length: { minimum: 5, maximum: 100 }
6 |
7 | validates :goal,
8 | presence: true, numericality: { greater_than: 20, less_than: 1000000 }
9 |
10 | validates :description,
11 | presence: true, length: { minimum: 10, maximum: 5000 }
12 | end
13 |
--------------------------------------------------------------------------------
/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 rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/app/views/admin/kaminari/_gap.html.erb:
--------------------------------------------------------------------------------
1 | <%# Non-link tag that stands for skipped pages...
2 | - available local variables
3 | current_page: a page object for the currently displayed page
4 | total_pages: total number of pages
5 | per_page: number of items to fetch per page
6 | remote: data-remote
7 | -%>
8 | <%= t('views.pagination.truncate').html_safe %>
9 |
--------------------------------------------------------------------------------
/config/initializers/kaminari_config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | Kaminari.configure do |config|
3 | config.default_per_page = 12
4 | # config.max_per_page = nil
5 | # config.window = 4
6 | # config.outer_window = 0
7 | # config.left = 0
8 | # config.right = 0
9 | # config.page_method_name = :page
10 | # config.param_name = :page
11 | # config.params_on_first_page = false
12 | end
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore bundler config.
2 | /.bundle
3 |
4 | # Ignore the default SQLite database.
5 | /db/*.sqlite3
6 | /db/*.sqlite3-journal
7 |
8 | # Ignore all logfiles and tempfiles.
9 | /log/*
10 | /tmp/*
11 | !/log/.keep
12 | !/tmp/.keep
13 |
14 | # Ignore Byebug command history file.
15 | .byebug_history
16 |
17 | # Ignore DS_store
18 | .DS_Store
19 |
20 | # Ignore ngrok for local testing
21 | ngrok
22 |
--------------------------------------------------------------------------------
/db/migrate/20160911193440_create_stripe_accounts.rb:
--------------------------------------------------------------------------------
1 | class CreateStripeAccounts < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :stripe_accounts do |t|
4 | t.string :first_name
5 | t.string :last_name
6 | t.string :account_type
7 | t.integer :dob_month
8 | t.integer :dob_day
9 | t.integer :dob_year
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/test/controllers/debit_cards_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class DebitCardsControllerTest < ActionDispatch::IntegrationTest
4 | include Devise::Test::IntegrationHelpers
5 |
6 | test "should get new" do
7 | sign_in @user
8 | create_stripe_account
9 | @user.stripe_account = @stripe_account.id
10 | get debit_cards_new_url
11 | assert_response :success
12 | end
13 |
14 | end
15 |
--------------------------------------------------------------------------------
/test/fixtures/stripe_accounts.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | first_name: MyString
5 | last_name: MyString
6 | account_type: MyString
7 | dob_month: 1
8 | dob_day: 1
9 | dob_year: 1
10 |
11 | two:
12 | first_name: MyString
13 | last_name: MyString
14 | account_type: MyString
15 | dob_month: 1
16 | dob_day: 1
17 | dob_year: 1
18 |
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the rails generate channel command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/db/migrate/20160911230223_add_fields_to_stripe_account.rb:
--------------------------------------------------------------------------------
1 | class AddFieldsToStripeAccount < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :stripe_accounts, :address_city, :string
4 | add_column :stripe_accounts, :address_state, :string
5 | add_column :stripe_accounts, :address_line1, :string
6 | add_column :stripe_accounts, :address_postal, :string
7 | add_column :stripe_accounts, :tos, :boolean
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/views/stripe_accounts/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Submit your info' %>
2 |
3 |
4 |
5 |
Submit your account application
6 |
7 | <%= render 'layouts/messages' %>
8 | <%= render 'account_form' %>
9 |
10 |
11 |
12 |
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 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/reset_password_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | Someone has requested a link to change your password. You can do this through the link below.
4 |
5 | <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
6 |
7 | If you didn't request this, please ignore this email.
8 | Your password won't change until you access the link above and create a new one.
9 |
--------------------------------------------------------------------------------
/db/migrate/20170405165023_add_more_fields_to_stripe_accounts.rb:
--------------------------------------------------------------------------------
1 | class AddMoreFieldsToStripeAccounts < ActiveRecord::Migration[5.0]
2 | def change
3 | add_column :stripe_accounts, :ssn_last_4, :string
4 | add_column :stripe_accounts, :business_name, :string
5 | add_column :stripe_accounts, :business_tax_id, :string
6 | add_column :stripe_accounts, :personal_id_number, :string
7 | add_column :stripe_accounts, :verification_document, :string
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 | rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
4 |
5 | # Pretty generic method to handle exceptions.
6 | # You'll probably want to do some logging, notifications, etc.
7 | def handle_error(message = "Sorry, something failed.", view = 'new')
8 | flash.now[:alert] = message
9 | render view
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/helpers/devise_helper.rb:
--------------------------------------------------------------------------------
1 | module DeviseHelper
2 | def devise_error_messages!
3 | return "" if resource.errors.empty?
4 | messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
5 | html = <<-HTML
6 |
10 | HTML
11 | html.html_safe
12 | end
13 | end
--------------------------------------------------------------------------------
/app/views/admin/kaminari/_last_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link to the "Last" page
2 | - available local variables
3 | url: url to the last page
4 | current_page: a page object for the currently displayed page
5 | total_pages: total number of pages
6 | per_page: number of items to fetch per page
7 | remote: data-remote
8 | -%>
9 |
10 | <%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote %>
11 |
12 |
--------------------------------------------------------------------------------
/app/views/admin/kaminari/_first_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link to the "First" page
2 | - available local variables
3 | url: url to the first page
4 | current_page: a page object for the currently displayed page
5 | total_pages: total number of pages
6 | per_page: number of items to fetch per page
7 | remote: data-remote
8 | -%>
9 |
10 | <%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote %>
11 |
12 |
--------------------------------------------------------------------------------
/app/views/layouts/_messages.html.erb:
--------------------------------------------------------------------------------
1 | <% flash.each do |key, value| %>
2 |
3 |
×
4 |
5 | <% if value.is_a? String %>
6 |
7 | <%= value %>
8 |
9 | <% else %>
10 | <% value.each do |val| %>
11 |
12 | <%= val %>
13 |
14 | <% end %>
15 | <% end %>
16 |
17 |
18 | <% end %>
--------------------------------------------------------------------------------
/app/models/stripe_account.rb:
--------------------------------------------------------------------------------
1 | class StripeAccount < ApplicationRecord
2 | validates :first_name,
3 | presence: true, length: { minimum: 1, maximum: 40 }
4 |
5 | validates :last_name,
6 | presence: true, length: { minimum: 1, maximum: 40 }
7 |
8 | validates :account_type,
9 | presence: true, inclusion: { in: %w(individual company), message: "%{value} is not a valid account type"}
10 |
11 | validates :tos,
12 | inclusion: { in: [ true ], message: ": You must agree to the terms of service" }
13 | end
14 |
--------------------------------------------------------------------------------
/app/views/admin/kaminari/_next_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link to the "Next" page
2 | - available local variables
3 | url: url to the next page
4 | current_page: a page object for the currently displayed page
5 | total_pages: total number of pages
6 | per_page: number of items to fetch per page
7 | remote: data-remote
8 | -%>
9 |
10 | <%= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote %>
11 |
12 |
--------------------------------------------------------------------------------
/app/views/devise/unlocks/new.html.erb:
--------------------------------------------------------------------------------
1 | Resend unlock instructions
2 |
3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
4 | <%= devise_error_messages! %>
5 |
6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true %>
9 |
10 |
11 |
12 | <%= f.submit "Resend unlock instructions" %>
13 |
14 | <% end %>
15 |
16 | <%= render "devise/shared/links" %>
17 |
--------------------------------------------------------------------------------
/app/views/admin/kaminari/_prev_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link to the "Previous" page
2 | - available local variables
3 | url: url to the previous page
4 | current_page: a page object for the currently displayed page
5 | total_pages: total number of pages
6 | per_page: number of items to fetch per page
7 | remote: data-remote
8 | -%>
9 |
10 | <%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote %>
11 |
12 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m))
11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) }
12 | gem 'spring', match[1]
13 | require 'spring/binstub'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/views/admin/kaminari/_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link showing page number
2 | - available local variables
3 | page: a page object for "this" page
4 | url: url to this page
5 | current_page: a page object for the currently displayed page
6 | total_pages: total number of pages
7 | per_page: number of items to fetch per page
8 | remote: data-remote
9 | -%>
10 |
11 | <%= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel} %>
12 |
13 |
--------------------------------------------------------------------------------
/app/views/campaigns/_verification.html.erb:
--------------------------------------------------------------------------------
1 | <% if @stripe_account.requirements.currently_due %>
2 |
3 |
4 |
5 |
6 | We need some information to continue processing donations for you.
7 |
8 |
9 | <%= link_to "Update your information", edit_stripe_account_path(@stripe_account.id), class: "btn btn-lg btn-warning btn-custom" %>
10 |
11 |
12 |
13 |
14 | <% end %>
15 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
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 SellerDashboard
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 | end
15 | end
16 |
--------------------------------------------------------------------------------
/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]
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 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | # This model initially had no columns defined. If you add columns to the
4 | # model remove the '{}' from the fixture names and add the columns immediately
5 | # below each fixture, per the syntax in the comments below
6 | #
7 | one: {
8 | id: 1,
9 | email: "email@hotmail.com",
10 | encrypted_password: "$2a$10$ZU0x7nDXA1EKbUAjwKTCwegV/yfddZkvfKWEa9/arb5pBQuy/1oFu"
11 | }
12 | # column: value
13 | #
14 | two: {
15 | email: user2@test.com
16 | }
17 | # column: value
18 |
--------------------------------------------------------------------------------
/app/views/kaminari/_paginator.html.erb:
--------------------------------------------------------------------------------
1 | <%= paginator.render do -%>
2 |
15 | <% end -%>
16 |
--------------------------------------------------------------------------------
/app/views/campaigns/_donations.html.erb:
--------------------------------------------------------------------------------
1 | <% @charges.each do | charge | %>
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= number_to_currency(charge.amount/100) %> donated by <%= charge.name.split.first %>
10 | <%= time_ago_in_words(charge.created_at) %> ago
11 |
12 |
13 |
14 |
15 | <% end %>
16 |
--------------------------------------------------------------------------------
/app/views/devise/confirmations/new.html.erb:
--------------------------------------------------------------------------------
1 | Resend confirmation instructions
2 |
3 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
4 | <%= devise_error_messages! %>
5 |
6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
9 |
10 |
11 |
12 | <%= f.submit "Resend confirmation instructions" %>
13 |
14 | <% end %>
15 |
16 | <%= render "devise/shared/links" %>
17 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/app/views/layouts/_footer.html.erb:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/app/views/campaigns/_transactions.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Balance amount
7 | Available for transfer after
8 |
9 |
10 |
11 | <% @transactions.each do |date,net| %>
12 | <% unless net.eql?(0) %>
13 |
14 | <%= format_amount(net) %>
15 | <%= format_date(date) %>
16 |
17 | <% end %>
18 | <% end %>
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= yield(:page_title) %>
8 | <%= csrf_meta_tags %>
9 | <%= stylesheet_link_tag 'application', media: 'all' %>
10 | <%= javascript_include_tag 'application' %>
11 | <%= favicon_link_tag 'favicon.ico' %>
12 | <%= yield(:header) %>
13 |
14 |
15 |
16 | <%= render 'layouts/header' %>
17 | <%= yield %>
18 | <%= render 'layouts/footer' %>
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/views/stripe_accounts/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Submit your account info' %>
2 |
3 |
4 |
5 |
6 | Update your account
7 |
8 |
9 | <%= render 'layouts/messages' %>
10 | <%= render 'identity_verification_form' %>
11 |
12 |
13 |
14 |
19 |
--------------------------------------------------------------------------------
/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/pages/terms.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Terms' %>
2 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/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 any plugin's vendor/assets/stylesheets directory 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 bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require font-awesome
14 | *= require_self
15 | *= require_tree .
16 | */
17 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | devise_for :users, controllers: { registrations: "registrations" }
4 |
5 | root to: "campaigns#home"
6 | get 'dashboard', to: 'campaigns#dashboard'
7 | get 'pricing', to: 'pages#pricing'
8 | get 'terms', to: 'pages#terms'
9 | get 'stripe_accounts/full', to: 'stripe_accounts#full'
10 | get 'debit_cards/new'
11 | post 'debit_cards/create', to: 'debit_cards#create'
12 | post 'debit_cards/destroy', to: 'debit_cards#destroy'
13 | post 'disputes', to: 'disputes#create'
14 | post 'instant_transfer', to: 'debit_cards#transfer'
15 | get 'payouts/:id', to: 'payouts#show', as: 'payout'
16 | post 'webhooks/stripe', to: 'webhooks#stripe'
17 |
18 | resources :campaigns
19 |
20 | resources :stripe_accounts
21 |
22 | resources :charges
23 |
24 | resources :bank_accounts
25 | end
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 any plugin's vendor/assets/javascripts directory 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 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require bootstrap
15 | //= require jquery_ujs
16 | //= require data-confirm-modal
17 | //= require_tree .
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/app/views/devise/passwords/edit.html.erb:
--------------------------------------------------------------------------------
1 | Change your password
2 |
3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
4 | <%= devise_error_messages! %>
5 | <%= f.hidden_field :reset_password_token %>
6 |
7 |
8 | <%= f.label :password, "New password" %>
9 | <% if @minimum_password_length %>
10 | (<%= @minimum_password_length %> characters minimum)
11 | <% end %>
12 | <%= f.password_field :password, autofocus: true, autocomplete: "off" %>
13 |
14 |
15 |
16 | <%= f.label :password_confirmation, "Confirm new password" %>
17 | <%= f.password_field :password_confirmation, autocomplete: "off" %>
18 |
19 |
20 |
21 | <%= f.submit "Change my password" %>
22 |
23 | <% end %>
24 |
25 | <%= render "devise/shared/links" %>
26 |
--------------------------------------------------------------------------------
/test/controllers/charges_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class ChargesControllerTest < ActionDispatch::IntegrationTest
4 | include Devise::Test::IntegrationHelpers
5 |
6 | test "should not show charges unless logged in" do
7 | get charge_path(@campaign)
8 | assert_redirected_to new_user_session_path
9 | end
10 |
11 | test "should require token to create a charge" do
12 | post charges_path, params: { amount: 100, name: "User", campaign: @campaign }
13 | assert_response :redirect
14 | end
15 |
16 | test "should create successful charge" do
17 | create_stripe_account
18 | @campaign.user_id = @user.id
19 | @user.stripe_account = @stripe_account.id
20 | @user.save
21 | charge_amount = rand(10..50)
22 | post charges_path, params: { amount: charge_amount, name: "User", campaign: @campaign.id, stripeToken: 'tok_visa' }
23 | assert_equal(charge_amount*100, Charge.last.amount)
24 | end
25 |
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/app/views/admin/kaminari/_paginator.html.erb:
--------------------------------------------------------------------------------
1 | <%# The container tag
2 | - available local variables
3 | current_page: a page object for the currently displayed page
4 | total_pages: total number of pages
5 | per_page: number of items to fetch per page
6 | remote: data-remote
7 | paginator: the paginator that renders the pagination tags inside
8 | -%>
9 | <%= paginator.render do -%>
10 |
25 | <% end -%>
26 |
--------------------------------------------------------------------------------
/config/secrets.yml:
--------------------------------------------------------------------------------
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 `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: cb8b29ab66181a6dbefabc9939b0c9e599a8788fd450a218caad32bac552aaa473836aa7286019b7d108fc4a968b7363f8814fa35b14efe4dd3fe85ee8f76421
15 |
16 | test:
17 | secret_key_base: 85767ecd3e5e4b26f92be965ec4bc5cc2850a13ef4999bb208039b68b80e020669ce791e3f91e048b80ad3693a5e45f61c77e61740eca164ccf32962882784b2
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | # puts "\n== Copying sample files =="
22 | # unless File.exist?('config/database.yml')
23 | # cp 'config/database.yml.sample', 'config/database.yml'
24 | # end
25 |
26 | puts "\n== Preparing database =="
27 | system! 'bin/rails db:setup'
28 |
29 | puts "\n== Removing old logs and tempfiles =="
30 | system! 'bin/rails log:clear tmp:clear'
31 |
32 | puts "\n== Restarting application server =="
33 | system! 'bin/rails restart'
34 | end
35 |
--------------------------------------------------------------------------------
/app/views/pages/pricing.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Pricing' %>
2 |
3 |
4 |
5 |
Simple, transparent pricing
6 |
7 |
10%
8 |
Fee on funds raised
9 |
10 |
11 |
12 |
$0
13 | Refunds
14 |
15 |
16 |
$15
17 | Dispute fee
18 |
19 |
20 |
$0
21 | Setup
22 |
23 |
24 |
25 |
26 | <%= link_to "Sign up for an account", new_user_registration_path, class: "btn btn-xl btn-primary btn-custom btn-block shadow-sm" %>
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/views/devise/passwords/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Reset your password' %>
2 |
3 |
4 |
5 |
Forgot your password?
6 | <%= devise_error_messages! %>
7 | <%= render 'layouts/messages' %>
8 |
9 |
10 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
11 |
12 | <%= f.label :email %>
13 | <%= f.email_field :email, autofocus: true, class: "form-control input-lg", placeholder: "Email address" %>
14 |
15 |
16 | <%= f.button "Reset password", class: "btn btn-lg btn-primary btn-block btn-custom", data: {disable_with: " Resetting..."} %>
17 |
18 | <% end %>
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/controllers/campaigns_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class CampaignsControllerTest < ActionDispatch::IntegrationTest
4 | include Devise::Test::IntegrationHelpers
5 |
6 | test "should load root path" do
7 | get root_path
8 | assert_response :success
9 | assert_select 'title', "Stripe Connect Example App"
10 | end
11 |
12 | test "should load campaign" do
13 | get campaign_path(@campaign)
14 | assert_response :success
15 | end
16 |
17 | test "should require login to create a campaign" do
18 | get new_campaign_path
19 | assert_redirected_to new_user_session_path
20 | end
21 |
22 | test "should redirect if no stripe account" do
23 | sign_in @user
24 | get new_campaign_path
25 | assert_redirected_to new_stripe_account_path
26 | end
27 |
28 | test 'should create a campaign successfully' do
29 | sign_in @user
30 | create_stripe_account
31 | @user.stripe_account = @stripe_account.id
32 | post campaigns_path, params: { campaign: { title: "Title", description: "Help me do a thing", goal: 100, image: "https://unsplash.it" } }
33 | assert_redirected_to campaign_path(Campaign.last)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/config/initializers/new_framework_defaults.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains migration options to ease your Rails 5.0 upgrade.
4 | #
5 | # Read the Rails 5.0 release notes for more info on each option.
6 |
7 | # Enable per-form CSRF tokens. Previous versions had false.
8 | Rails.application.config.action_controller.per_form_csrf_tokens = true
9 |
10 | # Enable origin-checking CSRF mitigation. Previous versions had false.
11 | Rails.application.config.action_controller.forgery_protection_origin_check = true
12 |
13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
14 | # Previous versions had false.
15 | ActiveSupport.to_time_preserves_timezone = true
16 |
17 | # Require `belongs_to` associations by default. Previous versions had false.
18 | Rails.application.config.active_record.belongs_to_required_by_default = true
19 |
20 | # Do not halt callback chains when a callback returns false. Previous versions had true.
21 | ActiveSupport.halt_callback_chains_on_return_false = false
22 |
23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false.
24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } }
25 |
--------------------------------------------------------------------------------
/test/controllers/bank_accounts_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class BankAccountsControllerTest < ActionDispatch::IntegrationTest
4 | include Devise::Test::IntegrationHelpers
5 |
6 | test "should redirect if not authenticated" do
7 | get new_bank_account_path
8 | assert_redirected_to new_user_session_path
9 | end
10 |
11 | test 'should not allow post if not authenticated' do
12 | post bank_accounts_path "stripeToken": "tok_123"
13 | assert_redirected_to new_user_session_path
14 | end
15 |
16 | test 'should redirect if no stripe account' do
17 | sign_in @user
18 | get new_bank_account_path
19 | assert_redirected_to new_stripe_account_path
20 | end
21 |
22 | test 'should load bank accounts page if stripe account exists' do
23 | sign_in @user
24 | @user.stripe_account = "acct_abc123"
25 | get new_bank_account_path
26 | assert_response :success
27 | end
28 |
29 | test 'should attach a valid bank account' do
30 | sign_in @user
31 | create_stripe_account
32 | @user.stripe_account = @stripe_account.id
33 | create_bank_token
34 | post bank_accounts_path, params: { stripeToken: @btok.id }
35 | assert_redirected_to dashboard_path
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/views/devise/shared/_links.html.erb:
--------------------------------------------------------------------------------
1 | <%- if controller_name != 'sessions' %>
2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end -%>
4 |
5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%>
8 |
9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%>
12 |
13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%>
16 |
17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%>
20 |
21 | <%- if devise_mapping.omniauthable? %>
22 | <%- resource_class.omniauth_providers.each do |provider| %>
23 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end -%>
25 | <% end -%>
26 |
--------------------------------------------------------------------------------
/app/views/debit_cards/_debit_card_form.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/campaigns/_checkoutform.html.erb:
--------------------------------------------------------------------------------
1 |
27 |
28 | Use Stripe Elements
29 |
30 |
--------------------------------------------------------------------------------
/app/views/campaigns/_campaigns.html.erb:
--------------------------------------------------------------------------------
1 | <% @campaigns.each_slice(3) do | campaigns | %>
2 |
3 | <% campaigns.each do |campaign| %>
4 |
5 |
6 | <%= link_to campaign_path(campaign) do %>
7 |
8 | <%= image_tag("#{campaign.image}_sm.jpg") %>
9 |
10 |
<%= link_to campaign.title.titleize, campaign_path(campaign) %>
11 |
12 | <% if campaign.raised %>
13 | <%= number_to_currency(campaign.raised/100, precision: 0) %> raised so far of <%= number_to_currency(campaign.goal, precision: 0) %> goal
14 | <% else %>
15 | <%= number_to_currency(campaign.goal, precision: 0) %> goal
16 | <% end %>
17 |
18 |
19 | <%= simple_format(truncate(campaign.description, length: 90)) %>
20 |
21 |
22 |
23 | <% end %>
24 |
25 |
26 | <% end %>
27 |
28 | <% end %>
29 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require File.expand_path('../../config/environment', __FILE__)
3 | require 'rails/test_help'
4 |
5 | Stripe.api_key = ENV['SECRET_KEY']
6 |
7 | class ActiveSupport::TestCase
8 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
9 | fixtures :all
10 |
11 | setup do
12 | @user = users(:one)
13 | @campaign = campaigns(:one)
14 | end
15 |
16 | # Create a US bank token
17 | def create_bank_token
18 | @btok = Stripe::Token.create(
19 | bank_account: {
20 | country: "US",
21 | currency: "usd",
22 | routing_number: "110000000",
23 | account_number: "000123456789"
24 | }
25 | )
26 | end
27 |
28 | # Create a Stripe account to use for tests
29 | def create_stripe_account
30 | @stripe_account = Stripe::Account.create(
31 | type: 'custom',
32 | requested_capabilities: ['platform_payments'], # Donors interact with the platform
33 | business_type: 'company',
34 | company: {
35 | name: 'Snookies Cookies',
36 | tax_id: '000000000',
37 | },
38 | business_profile: {
39 | product_description: 'Fundraising campaign',
40 | },
41 | tos_acceptance: {
42 | date: Time.now.to_i,
43 | ip: '1.2.3.4'
44 | },
45 | )
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/app/views/layouts/_header.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 | <% if user_signed_in? %>
15 | <%= link_to "Create a campaign", new_campaign_path %>
16 | <%= link_to "Dashboard", dashboard_path %>
17 | <%= link_to "Account", edit_user_registration_path %>
18 | <%= link_to "Sign out", destroy_user_session_path, method: :delete %>
19 | <% else %>
20 | <%= link_to("Pricing", pricing_path) %>
21 | <%= link_to "Sign up", new_user_registration_path %>
22 | <%= link_to "Sign in", new_user_session_path %>
23 | <% end %>
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/views/campaigns/_transfers.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Transfer ID
7 | Destination
8 | Amount
9 | Deposit date
10 |
11 |
12 |
13 | <% @payouts.each do |payout| %>
14 |
15 |
16 | <% if payout.type.eql?('bank_account') %>
17 |
18 | <%= link_to payout.id, payout_path(payout.id), class: "leftspace" %>
19 | <% else %>
20 |
21 | <%= payout.id %>
22 | <% end %>
23 |
24 |
25 | <% if payout.type.eql?('bank_account') %>
26 | <%= payout.destination.bank_name %> ending in <%= payout.destination.last4 %>
27 | <% elsif payout.type.eql?('card') %>
28 | <%= payout.destination.brand %> ending in <%= payout.destination.last4 %>
29 | <% end %>
30 |
31 | <%= format_amount(payout.amount) %>
32 | <%= format_date(payout.arrival_date) %>
33 |
34 | <% end %>
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/controllers/payouts_controller.rb:
--------------------------------------------------------------------------------
1 | class PayoutsController < ApplicationController
2 | before_action :authenticate_user!
3 |
4 | def show
5 | # Retrieve the payout from Stripe to get details
6 | # For large production applications, it's usually best to store this state locally
7 | if params[:id]
8 | begin
9 | @stripe_account = Stripe::Account.retrieve(current_user.stripe_account)
10 |
11 | # Get the payout details
12 | @payout = Stripe::Payout.retrieve(
13 | {
14 | id: params[:id]
15 | },
16 | { stripe_account: current_user.stripe_account }
17 | )
18 |
19 | # Get the balance transactions from the payout
20 | @txns = Stripe::BalanceTransaction.list(
21 | {
22 | payout: params[:id],
23 | expand: ['data.source.source_transfer', 'data.source.charge.source_transfer'],
24 | limit: 100
25 | },
26 | { stripe_account: current_user.stripe_account }
27 | )
28 | # Handle exceptions from Stripe
29 | rescue Stripe::StripeError => e
30 | flash[:error] = e.message
31 | redirect_to dashboard_path
32 | rescue => e
33 | # Something else happened, completely unrelated to Stripe
34 | flash[:error] = e.message
35 | redirect_to dashboard_path
36 | end
37 | else
38 | flash[:error] = "Sorry, this payout was not found"
39 | redirect_to dashboard_path
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/controllers/disputes_controller.rb:
--------------------------------------------------------------------------------
1 | class DisputesController < ApplicationController
2 | before_action :authenticate_user!
3 |
4 | def create
5 | if dispute_params[:dispute_text].empty? || dispute_params[:dispute_id].empty?
6 | flash[:error] = "Please provide supporting information about this dispute"
7 | redirect_back(fallback_location: root_path) and return
8 | end
9 |
10 | begin
11 | # Retrieve the account object for this user
12 | account = Stripe::Account.retrieve(current_user.stripe_account)
13 |
14 | # Retrieve the dispute
15 | dispute = Stripe::Dispute.retrieve(dispute_params[:dispute_id])
16 |
17 | # Add the dispute evidence
18 | dispute.evidence.uncategorized_text = dispute_params[:dispute_text]
19 | # Add dispute document if one exists
20 | dispute.evidence.uncategorized_file = dispute_params[:dispute_document]
21 | dispute.save
22 |
23 | # Success, send back to the page
24 | flash[:success] = "This dispute has been updated"
25 | redirect_back(fallback_location: root_path) and return
26 |
27 | # Handle exceptions from Stripe
28 | rescue Stripe::StripeError => e
29 | flash[:error] = e.message
30 | redirect_to dashboard_path
31 |
32 | # Handle any other exceptions
33 | rescue => e
34 | flash[:error] = e.message
35 | redirect_to dashboard_path
36 | end
37 | end
38 |
39 | private
40 | def dispute_params
41 | params.permit(:dispute_id, :dispute_text, :dispute_document)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/db/migrate/20160910151553_devise_create_users.rb:
--------------------------------------------------------------------------------
1 | class DeviseCreateUsers < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :users do |t|
4 | ## Database authenticatable
5 | t.string :email, null: false, default: ""
6 | t.string :encrypted_password, null: false, default: ""
7 |
8 | ## Recoverable
9 | t.string :reset_password_token
10 | t.datetime :reset_password_sent_at
11 |
12 | ## Rememberable
13 | t.datetime :remember_created_at
14 |
15 | ## Trackable
16 | t.integer :sign_in_count, default: 0, null: false
17 | t.datetime :current_sign_in_at
18 | t.datetime :last_sign_in_at
19 | t.string :current_sign_in_ip
20 | t.string :last_sign_in_ip
21 |
22 | ## Confirmable
23 | # t.string :confirmation_token
24 | # t.datetime :confirmed_at
25 | # t.datetime :confirmation_sent_at
26 | # t.string :unconfirmed_email # Only if using reconfirmable
27 |
28 | ## Lockable
29 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
30 | # t.string :unlock_token # Only if unlock strategy is :email or :both
31 | # t.datetime :locked_at
32 |
33 |
34 | t.timestamps null: false
35 | end
36 |
37 | add_index :users, :email, unique: true
38 | add_index :users, :reset_password_token, unique: true
39 | # add_index :users, :confirmation_token, unique: true
40 | # add_index :users, :unlock_token, unique: true
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/views/devise/sessions/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Sign in' %>
2 |
3 |
4 |
5 |
Sign in to your account
6 | <%= devise_error_messages! %>
7 | <%= render 'layouts/messages' %>
8 |
9 |
10 | <%= form_for(resource, as: resource_name,
11 | url: session_path(resource_name)) do |f| %>
12 |
13 | <%= f.label :email %>
14 | <%= f.email_field :email, autofocus: true, class: "form-control input-lg", placeholder: "Email address" %>
15 |
16 |
17 | <%= f.label :password %>
18 | <%= f.password_field :password, autocomplete: "off", class: "form-control input-lg", placeholder: "Password" %>
19 |
20 |
21 | <%= f.button "Log in", class: "btn btn-lg btn-block btn-primary btn-custom", data: {disable_with: " Logging in..."} %>
22 |
23 | <% end %>
24 |
25 | <%= link_to "Forgot your password?", new_user_password_path %>
26 |
27 |
28 |
29 |
30 | Need an account? <%= link_to "Sign up", new_user_registration_path %>
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/controllers/bank_accounts_controller.rb:
--------------------------------------------------------------------------------
1 | class BankAccountsController < ApplicationController
2 | before_action :authenticate_user!
3 |
4 | def new
5 | # Redirect if no stripe account exists yet
6 | unless current_user.stripe_account
7 | redirect_to new_stripe_account_path and return
8 | end
9 |
10 | begin
11 | # Retrieve the account object for this user
12 | @account = Stripe::Account.retrieve(current_user.stripe_account)
13 |
14 | # Handle exceptions from Stripe
15 | rescue Stripe::StripeError => e
16 | handle_error(e.message, 'new')
17 |
18 | # Handle any other exceptions
19 | rescue => e
20 | handle_error(e.message, 'new')
21 | end
22 | end
23 |
24 | def create
25 | # Redirect if no token is POSTed or the user doesn't have a Stripe account
26 | unless params[:stripeToken] && current_user.stripe_account
27 | redirect_to new_bank_account_path and return
28 | end
29 |
30 | begin
31 | # Retrieve the account object for this user
32 | account = Stripe::Account.retrieve(current_user.stripe_account)
33 |
34 | # Create the bank account
35 | account.external_account = params[:stripeToken]
36 | account.save
37 |
38 | # Success, send on to the dashboard
39 | flash[:success] = "Your bank account has been added!"
40 | redirect_to dashboard_path
41 |
42 | # Handle exceptions from Stripe
43 | rescue Stripe::StripeError => e
44 | handle_error(e.message, 'new')
45 |
46 | # Handle any other exceptions
47 | rescue => e
48 | handle_error(e.message, 'new')
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/test/controllers/stripe_accounts_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class StripeAccountsControllerTest < ActionDispatch::IntegrationTest
4 | include Devise::Test::IntegrationHelpers
5 |
6 | test "should redirect if not logged in" do
7 | get new_stripe_account_path
8 | assert_redirected_to new_user_session_path
9 | end
10 |
11 | test "should require existing stripe account to edit" do
12 | sign_in @user
13 | get edit_stripe_account_path("acct_123")
14 | assert_redirected_to dashboard_path
15 | end
16 |
17 | test "should load new account creation path" do
18 | sign_in @user
19 | get new_stripe_account_path
20 | assert_response :success
21 | end
22 |
23 | test "should reject invalid account details and throw an error" do
24 | sign_in @user
25 | post stripe_accounts_path, params: {
26 | stripe_account: {
27 | dob_day: "100",
28 | dob_month: "00",
29 | dob_year: "1992",
30 | account_type: "individual",
31 | tos: "true"
32 | }
33 | }
34 | assert_nil @user.stripe_account
35 | assert_not_empty flash
36 | end
37 |
38 | test "should successfully create an account" do
39 | sign_in @user
40 | post stripe_accounts_path, params: {
41 | stripe_account: {
42 | first_name: "Test",
43 | last_name: "Mctesterson",
44 | dob_day: "29",
45 | dob_month: "04",
46 | dob_year: "1992",
47 | account_type: "individual",
48 | tos: "true"
49 | }
50 | }
51 | @user.reload
52 | assert_match(/acct_/, @user.stripe_account)
53 | assert_redirected_to new_bank_account_path
54 | end
55 |
56 | end
57 |
--------------------------------------------------------------------------------
/app/helpers/stripe_accounts_helper.rb:
--------------------------------------------------------------------------------
1 | module StripeAccountsHelper
2 | def states
3 | [
4 | ['Alabama', 'AL'],
5 | ['Alaska', 'AK'],
6 | ['Arizona', 'AZ'],
7 | ['Arkansas', 'AR'],
8 | ['California', 'CA'],
9 | ['Colorado', 'CO'],
10 | ['Connecticut', 'CT'],
11 | ['Delaware', 'DE'],
12 | ['District of Columbia', 'DC'],
13 | ['Florida', 'FL'],
14 | ['Georgia', 'GA'],
15 | ['Hawaii', 'HI'],
16 | ['Idaho', 'ID'],
17 | ['Illinois', 'IL'],
18 | ['Indiana', 'IN'],
19 | ['Iowa', 'IA'],
20 | ['Kansas', 'KS'],
21 | ['Kentucky', 'KY'],
22 | ['Louisiana', 'LA'],
23 | ['Maine', 'ME'],
24 | ['Maryland', 'MD'],
25 | ['Massachusetts', 'MA'],
26 | ['Michigan', 'MI'],
27 | ['Minnesota', 'MN'],
28 | ['Mississippi', 'MS'],
29 | ['Missouri', 'MO'],
30 | ['Montana', 'MT'],
31 | ['Nebraska', 'NE'],
32 | ['Nevada', 'NV'],
33 | ['New Hampshire', 'NH'],
34 | ['New Jersey', 'NJ'],
35 | ['New Mexico', 'NM'],
36 | ['New York', 'NY'],
37 | ['North Carolina', 'NC'],
38 | ['North Dakota', 'ND'],
39 | ['Ohio', 'OH'],
40 | ['Oklahoma', 'OK'],
41 | ['Oregon', 'OR'],
42 | ['Pennsylvania', 'PA'],
43 | ['Puerto Rico', 'PR'],
44 | ['Rhode Island', 'RI'],
45 | ['South Carolina', 'SC'],
46 | ['South Dakota', 'SD'],
47 | ['Tennessee', 'TN'],
48 | ['Texas', 'TX'],
49 | ['Utah', 'UT'],
50 | ['Vermont', 'VT'],
51 | ['Virginia', 'VA'],
52 | ['Washington', 'WA'],
53 | ['West Virginia', 'WV'],
54 | ['Wisconsin', 'WI'],
55 | ['Wyoming', 'WY']
56 | ]
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/app/views/campaigns/_paymentform.html.erb:
--------------------------------------------------------------------------------
1 |
41 |
42 | Use Stripe Checkout
43 |
44 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Sign up' %>
2 |
3 |
4 |
5 |
Sign up
6 | <%= devise_error_messages! %>
7 | <%= render 'layouts/messages' %>
8 |
9 |
10 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
11 |
12 | <%= f.label :email %>
13 | <%= f.email_field :email, class: 'form-control input-lg', placeholder: "Email", autofocus: true %>
14 |
15 |
16 | <%= f.label :password %>
17 | <%= f.password_field :password, autocomplete: "off", class: 'form-control input-lg', placeholder: "Password" %>
18 |
19 |
20 | <%= f.label :password_confirmation %>
21 | <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control input-lg', placeholder: "Confirm password" %>
22 |
23 |
24 | <%= f.button "Sign up", class: "btn btn-primary btn-lg btn-block btn-custom", data: {disable_with: " Signing up..."} %>
25 |
26 | <% end %>
27 |
28 |
29 |
30 | Already have an account? <%= link_to "Sign in", new_user_session_path %>
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/views/campaigns/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Edit your campaign' %>
2 |
3 |
4 |
5 |
Edit your campaign
6 |
7 | <%= render 'layouts/messages' %>
8 |
9 |
10 | <%= form_for @campaign do | f | %>
11 |
12 | <%= f.label :title, "Title" %>
13 | <%= f.text_field :title, class: "form-control input-lg" %>
14 |
15 |
22 |
23 | <%= f.label :description, "Description" %>
24 | <%= f.text_area :description, class: "form-control input-lg" %>
25 |
26 |
27 | <%= render 'images' %>
28 |
29 |
30 | <%= f.button "Update Campaign", class: "btn btn-primary btn-lg btn-block btn-custom", data: {disable_with: " Updating campaign..."} %>
31 |
32 | <%= f.hidden_field :image %>
33 | <% end %>
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/views/campaigns/home.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Stripe Connect Example App' %>
2 | <% if params[:page] %>
3 |
4 | <% else %>
5 |
6 |
7 |
8 |
9 | <%= render 'layouts/messages' %>
10 |
Fundraising Marketplace
11 |
12 |
13 | <% if user_signed_in? %>
14 | <%= link_to "Create a campaign", new_campaign_path, class: "btn btn-xl btn-primary btn-custom btn-block shadow-sm" %>
15 | <% else %>
16 | <%= link_to "Create an account", new_user_registration_path, class: "btn btn-xl btn-primary btn-custom btn-block shadow-sm" %>
17 | <% end %>
18 |
19 |
20 |
21 |
22 |
23 | <% end %>
24 |
25 |
26 |
27 |
28 | <%= render 'campaigns' %>
29 |
30 |
31 |
32 | <%= page_entries_info @campaigns, entry_name: 'campaign' %>
33 |
34 |
35 | <%= link_to_previous_page @campaigns, 'Previous Page', class: "btn btn-primary btn-custom" %>
36 | <%= link_to_next_page @campaigns, 'Next Page', class: "btn btn-primary btn-custom" %>
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.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 public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => 'public, max-age=3600'
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/app/views/bank_accounts/_bank_account_form.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum, this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # The code in the `on_worker_boot` will be called if you are using
36 | # clustered mode by specifying a number of `workers`. After each worker
37 | # process is booted this block will be run, if you are using `preload_app!`
38 | # option you will want to use this block to reconnect to any threads
39 | # or connections that may have been created at application boot, Ruby
40 | # cannot share connections between processes.
41 | #
42 | # on_worker_boot do
43 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
44 | # end
45 |
46 | # Allow puma to be restarted by `rails restart` command.
47 | plugin :tmp_restart
48 |
--------------------------------------------------------------------------------
/app/views/campaigns/_images.html.erb:
--------------------------------------------------------------------------------
1 |
18 |
35 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.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.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join('tmp/caching-dev.txt').exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => 'public, max-age=172800'
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 |
34 | # Print deprecation notices to the Rails logger.
35 | config.active_support.deprecation = :log
36 |
37 | # Raise an error on page load if there are pending migrations.
38 | config.active_record.migration_error = :page_load
39 |
40 | # Debug mode disables concatenation and preprocessing of assets.
41 | # This option may cause significant delays in view rendering with a large
42 | # number of complex assets.
43 | config.assets.debug = true
44 |
45 | # Suppress logger output for asset requests.
46 | config.assets.quiet = true
47 |
48 | # Raises error for missing translations
49 | # config.action_view.raise_on_missing_translations = true
50 |
51 | # Use an evented file watcher to asynchronously detect changes in source code,
52 | # routes, locales, etc. This feature depends on the listen gem.
53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
54 |
55 | # Configure actionmailer for devise
56 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
57 | end
58 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 |
4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
5 | gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
6 | # Use Puma as the app server
7 | gem 'puma', '~> 3.0'
8 | # Use SCSS for stylesheets
9 | gem 'sass-rails', '~> 5.0'
10 | # Use Uglifier as compressor for JavaScript assets
11 | gem 'uglifier', '>= 1.3.0'
12 | # Use CoffeeScript for .coffee assets and views
13 | gem 'coffee-rails', '~> 4.2'
14 | # See https://github.com/rails/execjs#readme for more supported runtimes
15 | # gem 'therubyracer', platforms: :ruby
16 |
17 | # Use jquery as the JavaScript library
18 | gem 'jquery-rails'
19 |
20 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
21 | gem 'jbuilder', '~> 2.5'
22 | # Use Redis adapter to run Action Cable in production
23 | # gem 'redis', '~> 3.0'
24 | # Use ActiveModel has_secure_password
25 | # gem 'bcrypt', '~> 3.1.7'
26 |
27 | # Use Capistrano for deployment
28 | # gem 'capistrano-rails', group: :development
29 |
30 | # Stripe for payments... oh hell yes
31 | gem 'stripe'
32 | # Bootstrap for the pretty
33 | gem 'bootstrap-sass', '~> 3.3.6'
34 | # Use Font Awesome to make pretty icons
35 | gem 'font-awesome-rails'
36 | # Devise for use auth
37 | gem 'devise', '~> 4.2', '>= 4.2.1'
38 | # Confirmable modals
39 | gem 'data-confirm-modal'
40 | # Mailgun to gun the mails
41 | gem 'mailgun-ruby'
42 | # Paginate all the things
43 | gem 'kaminari'
44 |
45 | group :development, :test do
46 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
47 | gem 'byebug', platform: :mri
48 | # Use sqlite3 as the database for Active Record
49 | gem 'sqlite3'
50 | end
51 |
52 | group :development do
53 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
54 | gem 'web-console'
55 | gem 'listen', '~> 3.0.5'
56 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
57 | gem 'spring'
58 | gem 'spring-watcher-listen', '~> 2.0.0'
59 | end
60 |
61 | group :production do
62 | gem 'pg', '~> 0.18.4'
63 | end
64 |
65 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
66 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
67 |
--------------------------------------------------------------------------------
/app/views/campaigns/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Sell a course' %>
2 |
3 |
4 |
5 |
Create a campaign
6 |
7 | <%= render 'layouts/messages' %>
8 |
9 |
10 | <%= form_for @campaign do | campaign | %>
11 |
12 | <%= campaign.label :title, "Title" %>
13 | <%= campaign.text_field :title, class: "form-control input-lg", placeholder: "Help me pay my college tuition!", value: @campaign.title || @campaign_title %>
14 |
15 |
22 |
23 | <%= campaign.label :description, "Description" %>
24 | <%= campaign.text_area :description, class: "form-control input-lg", placeholder: "My tuition is expensive! Please donate if you want to help me pay it.", value: @campaign.description || @campaign_description, rows: 4 %>
25 |
26 |
27 | <%= campaign.label :image, "Select an image for your campaign" %>
28 | <%= render 'images' %>
29 | <%= campaign.hidden_field :image, value: @campaign.image || @campaign_image %>
30 |
31 |
32 | <%= campaign.button "Create Campaign", class: "btn btn-primary btn-lg btn-block btn-custom", data: {disable_with: " Creating campaign..."} %>
33 |
34 | <% end %>
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/controllers/debit_cards_controller.rb:
--------------------------------------------------------------------------------
1 | class DebitCardsController < ApplicationController
2 | before_action :authenticate_user!
3 |
4 | def new
5 | end
6 |
7 | def create
8 | # Redirect if no token is POSTed or the user doesn't have a Stripe account
9 | unless params[:stripeToken] && current_user.stripe_account
10 | redirect_to debit_cards_create_path and return
11 | end
12 |
13 | begin
14 | # Retrieve the account object for this user
15 | account = Stripe::Account.retrieve(current_user.stripe_account)
16 |
17 | # Create the bank account
18 | account.external_accounts.create(external_account: params[:stripeToken])
19 | account.save
20 |
21 | # Success, send on to the dashboard
22 | flash[:success] = "Your debit card has been added!"
23 | redirect_to dashboard_path
24 |
25 | # Handle exceptions from Stripe
26 | rescue Stripe::StripeError => e
27 | handle_error(e.message, 'new')
28 |
29 | # Handle any other exceptions
30 | rescue => e
31 | handle_error(e.message, 'new')
32 | end
33 | end
34 |
35 | def destroy
36 | end
37 |
38 | def transfer
39 | begin
40 | # Retrieve the account object for this user
41 | account = Stripe::Account.retrieve(current_user.stripe_account)
42 |
43 | # Create an instant payout
44 | payout = Stripe::Payout.create(
45 | {
46 | amount: params[:amount],
47 | currency: "usd",
48 | method: "instant",
49 | destination: params[:destination]
50 | },
51 | { stripe_account: account.id }
52 | )
53 |
54 | # Take a 3% fee for the instant payout
55 | Stripe::Charge.create(
56 | amount: params[:fee],
57 | currency: "usd",
58 | source: account.id,
59 | description: "Instant payout fee for #{payout.id}"
60 | )
61 |
62 | # Success, send on to the dashboard
63 | flash[:success] = "Your payout has been made!"
64 | redirect_to dashboard_path
65 |
66 | # Handle exceptions from Stripe
67 | rescue Stripe::StripeError => e
68 | flash[:error] = e.message
69 | redirect_to dashboard_path
70 |
71 | # Handle any other exceptions
72 | rescue => e
73 | flash[:error] = e.message
74 | redirect_to dashboard_path
75 | end
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/app/views/campaigns/dashboard.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Dashboard' %>
2 |
3 |
4 |
5 | <%= render 'layouts/messages' %>
6 |
7 |
8 | <% unless @stripe_account.requirements.currently_due.empty? %>
9 | <%= render 'verification' %>
10 | <% end %>
11 |
12 | <% unless @campaigns.empty? %>
13 | <%= render 'campaigns' %>
14 | <% else %>
15 | <%= link_to "Create your first fundraising campaign to get started", new_campaign_path %>.
16 | <% end %>
17 |
21 |
22 |
23 | <% unless @payments.data.empty? %>
24 | <%= render 'charges' %>
25 | <% if @balance_available > 0 %>
26 |
27 |
Want your funds immediately?
28 |
29 | For an extra 3% fee, we'll create an instant transfer to your debit card.
30 |
31 | <% if @debit_card %>
32 | <%= button_to "Get this transfer instantly", instant_transfer_path, data: { confirm: "Instant payout #{format_amount(@instant_amt)} to your #{@debit_card.brand} debit card ending in #{@debit_card.last4}?", disable_with: "Transferring funds..." }, method: :post, params: { amount: @instant_amt.round, fee: @instant_fee.round, destination: @debit_card.id }, class: "btn btn-success btn-lg btn-custom" %>
33 | <% else %>
34 | <%= link_to "Add a debit card", debit_cards_new_path, class: "btn btn-lg btn-success btn-custom" %>
35 | <% end %>
36 |
37 | <% end %>
38 | <% else %>
39 | No donations yet.
40 | <% end %>
41 |
42 |
43 |
44 |
45 |
46 | <% unless @payouts.data.empty? %>
47 | <%= render 'transfers' %>
48 | <% else %>
49 | No payouts yet.
50 | <% end %>
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Your account' %>
2 |
3 |
4 |
5 |
Manage your account
6 | <%= devise_error_messages! %>
7 | <%= render 'layouts/messages' %>
8 |
9 |
10 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
11 | <%= devise_error_messages! %>
12 |
13 |
14 | <%= f.label :email %>
15 | <%= f.email_field :email, class: 'form-control input-lg', placeholder: "Email", autofocus: true %>
16 |
17 |
18 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
19 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
20 | <% end %>
21 |
22 |
23 | <%= f.password_field :password, autocomplete: "off", class: 'form-control input-lg', placeholder: "New password" %>
24 | Leave blank if you don't want to change your password
25 |
26 |
27 |
28 | <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control input-lg', placeholder: "Confirm new password" %>
29 |
30 |
31 |
32 | <%= f.password_field :current_password, autocomplete: "off", class: 'form-control input-lg', placeholder: "Current password" %>
33 |
34 |
35 |
36 | <%= f.submit "Update", class: "btn btn-primary btn-lg btn-block btn-custom", data: {disable_with: " Updating your account..."} %>
37 |
38 | <% end %>
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure you want to cancel your account? You can't undo this" }, method: :delete, class: "btn btn-danger btn-lg btn-block btn-custom" %>
47 |
48 |
49 | <%= link_to "Back", :back %>
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/views/campaigns/_charges.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Payment ID
7 | Amount
8 | Net
9 | Created
10 | Actions
11 |
12 |
13 |
14 | <% @payments.each do |payment| %>
15 | <% if payment.source_transfer.source_transaction.present? %>
16 |
17 |
18 | <% if payment.source_transfer.source_transaction.source.object.eql?('card') %>
19 |
20 | <% end %>
21 | <%= link_to payment.source_transfer.source_transaction.id, charge_path(payment.source_transfer.source_transaction.id), class: "leftspace" %>
22 | <% if payment.source_transfer.source_transaction.dispute.present? %>
23 | <% if payment.source_transfer.source_transaction.dispute.status.eql?('needs_response') %>
24 | Disputed
25 | <% elsif payment.source_transfer.source_transaction.dispute.status.eql?('lost') %>
26 | Dispute lost
27 | <% elsif payment.source_transfer.source_transaction.dispute.status.eql?('won') %>
28 | Dispute won
29 | <% end %>
30 | <% end %>
31 |
32 | <%= number_to_currency(payment.amount/100) %>
33 | <%= format_amount(payment.amount - payment.application_fee.amount) %>
34 | <%= format_date(payment.created) %>
35 |
36 | <% if payment.refunded %>
37 |
38 | Refunded
39 |
40 | <% else %>
41 | <%= link_to "Refund", charge_path(payment.source_transfer.source_transaction.id), method: :delete, data: { confirm: "Are you sure you want to refund this charge?", disable_with: " Refunding charge..." }, class: "btn btn-sm btn-block btn-custom btn-danger" %>
42 | <% end %>
43 |
44 |
45 | <% end %>
46 | <% end %>
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/views/bank_accounts/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, "Add a new bank account" %>
2 | <% content_for(:header) do %>
3 |
4 |
5 |
49 | <% end %>
50 |
51 |
52 |
53 |
Add a bank account
54 |
55 | <%= render 'layouts/messages' %>
56 | <%= render 'bank_account_form' %>
57 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20170502170520) do
14 |
15 | create_table "campaigns", force: :cascade do |t|
16 | t.integer "user_id"
17 | t.string "title"
18 | t.text "description"
19 | t.boolean "subscription"
20 | t.datetime "created_at", null: false
21 | t.datetime "updated_at", null: false
22 | t.string "image"
23 | t.integer "goal"
24 | t.integer "raised"
25 | t.boolean "active", default: true
26 | end
27 |
28 | create_table "charges", force: :cascade do |t|
29 | t.string "charge_id"
30 | t.integer "amount"
31 | t.integer "amount_refunded"
32 | t.integer "campaign_id"
33 | t.datetime "created_at", null: false
34 | t.datetime "updated_at", null: false
35 | t.string "name"
36 | end
37 |
38 | create_table "stripe_accounts", force: :cascade do |t|
39 | t.string "first_name"
40 | t.string "last_name"
41 | t.string "account_type"
42 | t.integer "dob_month"
43 | t.integer "dob_day"
44 | t.integer "dob_year"
45 | t.datetime "created_at", null: false
46 | t.datetime "updated_at", null: false
47 | t.string "address_city"
48 | t.string "address_state"
49 | t.string "address_line1"
50 | t.string "address_postal"
51 | t.boolean "tos"
52 | t.string "ssn_last_4"
53 | t.string "business_name"
54 | t.string "business_tax_id"
55 | t.string "personal_id_number"
56 | t.string "verification_document"
57 | t.string "acct_id"
58 | end
59 |
60 | create_table "users", force: :cascade do |t|
61 | t.string "email", default: "", null: false
62 | t.string "encrypted_password", default: "", null: false
63 | t.string "reset_password_token"
64 | t.datetime "reset_password_sent_at"
65 | t.datetime "remember_created_at"
66 | t.integer "sign_in_count", default: 0, null: false
67 | t.datetime "current_sign_in_at"
68 | t.datetime "last_sign_in_at"
69 | t.string "current_sign_in_ip"
70 | t.string "last_sign_in_ip"
71 | t.datetime "created_at", null: false
72 | t.datetime "updated_at", null: false
73 | t.string "stripe_account"
74 | t.index ["email"], name: "index_users_on_email", unique: true
75 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
76 | end
77 |
78 | end
79 |
--------------------------------------------------------------------------------
/app/views/payouts/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, "View payout" %>
2 |
3 |
4 |
5 |
Payout details
6 |
7 | <%= render 'layouts/messages' %>
8 |
9 |
10 |
11 |
Payout overview
12 |
13 |
14 | Date paid
15 | <%= format_date(@payout.created) %>
16 |
17 |
18 | Payout amount
19 | <%= format_amount(@payout.amount) %>
20 |
21 | <% if @payout.bank_account %>
22 |
23 | Bank account
24 | <%= "#{@payout.bank_account.bank_name} ending in #{@payout.bank_account.last4}" %>
25 |
26 | <% end %>
27 |
28 |
29 |
30 |
31 |
Payout transactions
32 |
33 |
34 |
35 |
36 | Type
37 | Gross
38 | Fee
39 | Total
40 |
41 |
42 |
43 | <% @txns.auto_paging_each do |txn| %>
44 | <% unless txn.type.eql?('transfer') && txn.source.type.eql?('bank_account') %>
45 |
46 |
47 | <% if txn.type.eql?('payment') && txn.source.source_transfer.source_transaction.present? %>
48 | <%= link_to "Payment", charge_path(txn.source.source_transfer.source_transaction) %>
49 | <% elsif txn.type.eql?('payment_refund') %>
50 | <%= link_to "Payment refund", charge_path(txn.source.charge.source_transfer.source_transaction) %>
51 | <% elsif txn.type.eql?('adjustment') %>
52 | Marketplace fee refund
53 | <% elsif txn.type.eql?('transfer') && txn.source.method.eql?('instant') %>
54 | Instant payout <%= txn.source.id %>
55 | <% elsif txn.type.eql?('transfer') && txn.source.type.eql?('stripe_account') %>
56 | <%= txn.description %>
57 | <% elsif txn.description.present? %>
58 | <%= txn.description %>
59 | <% else %>
60 | <%= txn.type %>
61 | <% end %>
62 |
63 | <%= format_amount(txn.amount) %>
64 | <%= format_amount(txn.fee) %>
65 | <%= format_amount(txn.net) %>
66 |
67 | <% end %>
68 | <% end %>
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/app/controllers/webhooks_controller.rb:
--------------------------------------------------------------------------------
1 | class WebhooksController < ApplicationController
2 | protect_from_forgery except: :stripe
3 |
4 | def stripe
5 | # Use signed webhooks
6 | endpoint_secret = ENV['ENDPOINT_SECRET'].to_s
7 |
8 | payload = request.body.read
9 | sig_header = request.env['HTTP_STRIPE_SIGNATURE']
10 | event = nil
11 |
12 | begin
13 | event = Stripe::Webhook.construct_event(
14 | payload, sig_header, endpoint_secret
15 | )
16 |
17 | case event.type
18 |
19 | #############
20 | # Disputes
21 | #############
22 |
23 | # Recover dispute amounts and fees for disputed charges
24 | when 'charge.dispute.created'
25 | # The dispute that was created
26 | dispute = event.data.object
27 |
28 | # Retrieve the charge related to this dispute
29 | charge = Stripe::Charge.retrieve(dispute.charge)
30 |
31 | # Retrieve the platform account ID. You could also store this in an env variable.
32 | platform_account = Stripe::Account.retrieve
33 |
34 | # Issue a transfer reversal to recover the funds
35 | reverse_transfer(charge)
36 |
37 | # Create an account debit to recover dispute fee
38 | debit = Stripe::Transfer.create(
39 | {
40 | amount: dispute.balance_transactions.first.fee,
41 | currency: "usd",
42 | destination: platform_account.id,
43 | description: "Dispute fee for #{charge.id}"
44 | },
45 | { stripe_account: charge.destination }
46 | )
47 |
48 | # Return charge amount and fees if dispute is won
49 | when 'charge.dispute.funds_reinstated'
50 | # The dispute that was created
51 | dispute = event.data.object
52 |
53 | # Retrieve the charge related to this dispute
54 | charge = Stripe::Charge.retrieve(dispute.charge)
55 |
56 | # Create a transfer to the connected account to return the dispute fee
57 | transfer = Stripe::Transfer.create(
58 | amount: dispute.balance_transactions.second.net,
59 | currency: "usd",
60 | destination: charge.destination
61 | )
62 |
63 | # Retrieve the destination payment
64 | payment = Stripe::Charge.retrieve(
65 | {
66 | id: transfer.destination_payment
67 | },
68 | {
69 | stripe_account: transfer.destination
70 | }
71 | )
72 |
73 | # Update the description on the destination payment
74 | payment.description = "Chargeback reversal for #{charge.id}"
75 | payment.save
76 |
77 | end
78 |
79 | # Something bad happened with the event or retrieving details from Stripe: probably log this.
80 | rescue JSON::ParserError, Stripe::SignatureVerificationError, Stripe::StripeError => e
81 | head :bad_request
82 |
83 | # Handle other exceptions. You may want to log these for review later too.
84 | rescue => e
85 | head :bad_request
86 | end
87 |
88 | # Return a 200
89 | head :ok
90 | end
91 |
92 | private
93 | def reverse_transfer(charge)
94 | # Retrieve the transfer for the charge
95 | transfer = Stripe::Transfer.retrieve(charge.transfer)
96 |
97 | # Reverse the transfer and keep the application fee
98 | transfer.reversals.create
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/app/views/charges/_dispute.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
Respond to this dispute
3 |
4 |
28 | <%= hidden_field_tag :authenticity_token, form_authenticity_token -%>
29 | <%= hidden_field_tag :dispute_id, @charge.dispute.id -%>
30 |
31 |
32 |
33 |
34 |
35 |
86 |
--------------------------------------------------------------------------------
/app/views/charges/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, 'Fundraising Marketplace | Review charge' %>
2 |
3 |
4 |
5 | <%= render 'layouts/messages' %>
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 | From
17 | <%= @charge.metadata.name %>
18 |
19 | Payment method
20 | <%= @charge.source.brand %> ending in <%= @charge.source.last4 %>
21 |
22 | For
23 | <%= link_to @campaign.title, campaign_path(@campaign) %>
24 |
25 | Marketplace fee
26 | <%= format_amount(@charge.application_fee.amount) %>
27 |
28 | Net earnings
29 | <%= format_amount(@charge.amount - @charge.application_fee.amount) %>
30 |
31 | Created
32 | <%= format_date(@charge.created) %>
33 |
34 | Refunded
35 | <%= @charge.refunded %>
36 |
37 | Disputed
38 | <%= @charge.dispute.present? %>
39 |
40 |
41 | <% if @charge.refunded %>
42 |
This charge has been refunded
43 | <% elsif @charge.dispute.present? %>
44 |
45 | <% if @charge.dispute.status.eql?('needs_response') %>
46 | Needs response
47 | This donation was disputed. <%= format_amount(@charge.dispute.balance_transactions.first.net.abs) %> in fees and disputed funds have been withdrawn from your account.
48 | <% elsif @charge.dispute.status.eql?('won') %>
49 | Won
50 | This dispute was won in your favor. <%= format_amount(@charge.dispute.balance_transactions.first.net.abs) %> in fees and disputed funds have been returned to your account.
51 | <% elsif @charge.dispute.status.eql?('lost') %>
52 | Lost
53 | The bank sided in favor of the donor. <%= format_amount(@charge.dispute.balance_transactions.first.net.abs) %> in fees and disputed funds have been withdrawn from your account.
54 | <% end %>
55 |
56 | <% else %>
57 | <%= link_to "Refund this donation", charge_path(@charge.id), method: :delete, data: { confirm: "Are you sure you want to refund this charge?", disable_with: "
Refunding charge..." }, class: "btn btn-lg btn-block btn-custom btn-danger" %>
58 | <% end %>
59 |
60 |
61 | <% if @charge.dispute.present? && (@charge.dispute.status.eql?('needs_response') || @charge.dispute.status.eql?('under_review')) %>
62 | <%= render 'dispute' %>
63 | <% end %>
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/views/debit_cards/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, "Add debit card" %>
2 | <% content_for(:header) do %>
3 |
4 |
79 | <% end %>
80 |
81 |
82 |
83 |
Add a debit card
84 |
85 |
86 | <%= render 'layouts/messages' %>
87 | <%= render 'debit_card_form' %>
88 |
89 |
90 | Hint: use 4000056655665556 and any valid expiry.
91 |
92 |
93 | See all test debit card numbers
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.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 threaded 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 | # Disable serving static files from the `/public` folder by default since
18 | # Apache or NGINX already handles this.
19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
20 |
21 | # Compress JavaScripts and CSS.
22 | config.assets.js_compressor = :uglifier
23 | # config.assets.css_compressor = :sass
24 |
25 | # Do not fallback to assets pipeline if a precompiled asset is missed.
26 | config.assets.compile = false
27 |
28 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
29 |
30 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
31 | # config.action_controller.asset_host = 'http://assets.example.com'
32 |
33 | # Specifies the header that your server uses for sending files.
34 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
35 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
36 |
37 | # Mount Action Cable outside main process or domain
38 | # config.action_cable.mount_path = nil
39 | # config.action_cable.url = 'wss://example.com/cable'
40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
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 | # Use the lowest log level to ensure availability of diagnostic information
46 | # when problems arise.
47 | config.log_level = :debug
48 |
49 | # Prepend all log lines with the following tags.
50 | config.log_tags = [ :request_id ]
51 |
52 | # Use a different cache store in production.
53 | # config.cache_store = :mem_cache_store
54 |
55 | # Use a real queuing backend for Active Job (and separate queues per environment)
56 | # config.active_job.queue_adapter = :resque
57 | # config.active_job.queue_name_prefix = "seller-dashboard_#{Rails.env}"
58 | config.action_mailer.perform_caching = false
59 |
60 | # Ignore bad email addresses and do not raise email delivery errors.
61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
62 | # config.action_mailer.raise_delivery_errors = false
63 |
64 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
65 | # the I18n.default_locale when a translation cannot be found).
66 | config.i18n.fallbacks = true
67 |
68 | # Send deprecation notices to registered listeners.
69 | config.active_support.deprecation = :notify
70 |
71 | # Use default logging formatter so that PID and timestamp are not suppressed.
72 | config.log_formatter = ::Logger::Formatter.new
73 |
74 | # Use a different logger for distributed setups.
75 | # require 'syslog/logger'
76 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
77 |
78 | if ENV["RAILS_LOG_TO_STDOUT"].present?
79 | logger = ActiveSupport::Logger.new(STDOUT)
80 | logger.formatter = config.log_formatter
81 | config.logger = ActiveSupport::TaggedLogging.new(logger)
82 | end
83 |
84 | # Do not dump schema after migrations.
85 | config.active_record.dump_schema_after_migration = false
86 | end
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fundraising Marketplace: A Stripe Connect example app
2 |
3 | An example application built using [Stripe Connect](https://stripe.com/docs/connect) [custom accounts](https://stripe.com/docs/connect/custom-accounts). **This application is provided as an example, but isn't meant to be run in production.**
4 |
5 | [](https://stripe-marketplace-demo.herokuapp.com)
6 |
7 | [](https://stripe-marketplace-demo.herokuapp.com)
8 |
9 | [](https://stripe-marketplace-demo.herokuapp.com)
10 |
11 |
12 | ## Demo
13 | **You can find a working demo of this application deployed and running in test mode at https://stripe-marketplace-demo.herokuapp.com/**. Feel free to create an account, create a campaign, and make donations to see some data populated in the dashboard. You can find [test card numbers](https://stripe.com/docs/testing#cards), [bank accounts](https://stripe.com/docs/testing#managed-accounts), and [identity verification](https://stripe.com/docs/connect/testing) details in Stripe's documentation.
14 |
15 |
16 | ## Features
17 | :money_with_wings: Create fundraising campaigns and custom Stripe Connect accounts.
18 |
19 | :lock: Uses [Devise](https://github.com/plataformatec/devise) for user authentication.
20 |
21 | :chart_with_upwards_trend: Fairly complete seller dashboard to view charges, create refunds, view payouts, etc.
22 |
23 | :iphone: Fully responsive for mobile browsers.
24 |
25 | :bank: Add and modify connected [bank accounts](https://stripe.com/docs/api#account_create_bank_account).
26 |
27 | :credit_card: Make donations with either [Stripe Elements](https://stripe.com/docs/elements) or [Stripe Checkout](https://stripe.com/docs/checkout). [Make successful donations](https://stripe.com/docs/testing#cards) using valid test card numbers or see declines using test cards.
28 |
29 | :sunglasses: Take a 10% [application fee](https://stripe.com/docs/connect/charges) from connected accounts for successful charges.
30 |
31 | :zap: Create payouts to debit cards using [instant payouts](https://stripe.com/docs/connect/payouts#instant-payouts) and take a 3% platform fee in return using [account debits](https://stripe.com/docs/connect/account-debits).
32 |
33 | :clipboard: Identity verification example form and dashboard prompt to work through the [identity verification](https://stripe.com/docs/connect/identity-verification) process. Includes examples of collecting all info up front vs incrementally.
34 |
35 | :poop: [Create disputes](https://stripe.com/docs/testing#disputes) and use [webhooks](https://stripe.com/docs/webhooks) to recover funds + dispute fees automatically via account debits.
36 |
37 | :arrow_right_hook: Includes [webhook signature validation](https://stripe.com/docs/webhooks#signatures) for enhanced security.
38 |
39 |
40 | ## Shortcomings, things still needed
41 | * Still pretty basic integration tests.
42 | * Email receipts/notifications/etc.
43 | * Additional features like pagination for charges/transfers, ACH payments, alternative payment methods, etc.
44 |
45 |
46 | ## Specs
47 | Built on Rails 5 and running Ruby 2.2. Uses the 2019-02-19 API version, which includes [major chages](https://stripe.com/docs/upgrades#2019-02-19) to the way Connect onboarding works.
48 |
49 |
50 | ## Setup
51 | To run this locally, clone the repository and run bundler to install dependencies:
52 |
53 | ```
54 | git clone https://github.com/adam-stripe/stripe-connect-managed-rails.git
55 | cd stripe-connect-managed-rails
56 | bundle install
57 | ```
58 |
59 | Migrate:
60 |
61 | ```
62 | $ rails db:migrate
63 | ```
64 |
65 | Retrieve your [Stripe API keys](https://dashboard.stripe.com/account/apikeys) and set them as environment variables. You can run this app locally by starting Rails server:
66 |
67 | ```
68 | PUBLISHABLE_KEY=YOUR_STRIPE_PUBLISHABLE_KEY SECRET_KEY=YOUR_STRIPE_SECRET_KEY ENDPOINT_SECRET=YOUR_WEBHOOK_ENDPOINT_SECRET rails s
69 | ```
70 |
--------------------------------------------------------------------------------
/config/locales/devise.en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n
2 |
3 | en:
4 | devise:
5 | confirmations:
6 | confirmed: "Your email address has been successfully confirmed."
7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
9 | failure:
10 | already_authenticated: "You are already signed in."
11 | inactive: "Your account is not activated yet."
12 | invalid: "Invalid %{authentication_keys} or password."
13 | locked: "Your account is locked."
14 | last_attempt: "You have one more attempt before your account is locked."
15 | not_found_in_database: "Invalid %{authentication_keys} or password."
16 | timeout: "Your session expired. Please sign in again to continue."
17 | unauthenticated: "You need to sign in or sign up before continuing."
18 | unconfirmed: "You have to confirm your email address before continuing."
19 | mailer:
20 | confirmation_instructions:
21 | subject: "Confirmation instructions"
22 | reset_password_instructions:
23 | subject: "Reset password instructions"
24 | unlock_instructions:
25 | subject: "Unlock instructions"
26 | password_change:
27 | subject: "Password Changed"
28 | omniauth_callbacks:
29 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
30 | success: "Successfully authenticated from %{kind} account."
31 | passwords:
32 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
33 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
34 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
35 | updated: "Your password has been changed successfully. You are now signed in."
36 | updated_not_active: "Your password has been changed successfully."
37 | registrations:
38 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
39 | signed_up: "Welcome! Add your information below to create a fundraising campaign."
40 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
41 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
42 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
43 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
44 | updated: "Your account has been updated successfully."
45 | sessions:
46 | signed_in: "Signed in successfully."
47 | signed_out: "Signed out successfully."
48 | already_signed_out: "Signed out successfully."
49 | unlocks:
50 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
51 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
52 | unlocked: "Your account has been unlocked successfully. Please sign in to continue."
53 | errors:
54 | messages:
55 | already_confirmed: "was already confirmed, please try signing in"
56 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
57 | expired: "has expired, please request a new one"
58 | not_found: "not found"
59 | not_locked: "was not locked"
60 | not_saved:
61 | one: "1 error prohibited this %{resource} from being saved:"
62 | other: "%{count} errors prohibited this %{resource} from being saved:"
63 |
--------------------------------------------------------------------------------
/app/views/stripe_accounts/_account_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= form_for @account do | f | %>
4 |
5 |
6 |
7 | <%= f.label :account_type, "Account Type" %>
8 | <%= f.select(:account_type, options_for_select([["Individual","individual"], ["Company","company"]], @account[:account_type]), {}, { class: "form-control input-lg" }) %>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | <%= f.label :business_name, "Business name (as it appears to the IRS)" %>
17 | <%= f.text_field :business_name, class: "form-control input-lg", placeholder: "My Business LLC" %>
18 |
19 |
20 |
21 |
22 |
23 |
24 | <%= f.label :business_tax_id, "Business tax ID/EIN" %>
25 | <%= f.text_field :business_tax_id, class: "form-control input-lg", placeholder: "27-0000000" %>
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | <%= f.label :first_name, "First Name" %>
34 | <%= f.text_field :first_name, class: "form-control input-lg", placeholder: "Jane" %>
35 |
36 |
37 |
38 |
39 | <%= f.label :last_name, "Last Name" %>
40 | <%= f.text_field :last_name, class: "form-control input-lg", placeholder: "Doe" %>
41 |
42 |
43 |
44 |
45 |
46 |
47 | <%= f.label :ssn_last_4, "Last 4 digits of your SSN" %>
48 | <%= f.text_field :ssn_last_4, class: "form-control input-lg", placeholder: "6789" %>
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | <%= f.label :dob_month, "DOB Month" %>
57 | <%= f.select(:dob_month, options_for_select([["January", "1"],["February", "2"],["March", "3"],["April", "4"],["May", "5"],["June", "6"],["July", "7"],["August", "8"],["September", "9"],["October", "10"],["November", "11"],["December", "12"]], @account[:dob_month]), {}, { class: "form-control input-lg" }) %>
58 |
59 |
60 |
61 |
62 | <%= f.label :dob_day, "DOB Day" %>
63 | <%= f.select(:dob_day, options_for_select((1..31).each{|n| [n, n]}, @account[:dob_day]), {}, { class: "form-control input-lg" }) %>
64 |
65 |
66 |
67 |
68 | <%= f.label :dob_year, "DOB Year" %>
69 | <%= f.select(:dob_year, options_for_select((1912..2016).each{|n| [n, n]}, @account[:dob_year]), {}, { class: "form-control input-lg" }) %>
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | <%= f.check_box :tos %>
78 | <%= f.label :tos do %>
79 | I agree to the <%= link_to "terms of service", terms_path %>.
80 | <% end %>
81 |
82 |
83 |
84 |
85 |
86 |
87 | <%= f.button "Create Account", class: "btn btn-primary btn-lg btn-block btn-custom", data: {disable_with: " Creating account..."} %>
88 |
89 |
90 |
91 | <% end %>
92 |
93 |
94 |
105 |
--------------------------------------------------------------------------------
/app/controllers/charges_controller.rb:
--------------------------------------------------------------------------------
1 | class ChargesController < ApplicationController
2 | before_action :authenticate_user!, only: [:show, :destroy]
3 |
4 | def create
5 | # Check for a stripeToken
6 | unless charge_params[:stripeToken]
7 | flash[:error] = "No payment information submitted."
8 | redirect_back(fallback_location: root_path) and return
9 | end
10 |
11 | # Check for a valid campaign ID
12 | unless charge_params[:campaign] && Campaign.exists?(charge_params[:campaign])
13 | flash[:error] = "The campaign you specified doesn't exist."
14 | redirect_back(fallback_location: root_path) and return
15 | end
16 |
17 | # Retrieve the campaign
18 | campaign = Campaign.find(params[:campaign])
19 |
20 | begin
21 | # Find the account ID associated with this campaign
22 | account_id = User.find(campaign.user_id).stripe_account
23 |
24 | # Convert the amount to cents
25 | amount = (100 * charge_params[:amount].tr('$', '').to_r).to_i
26 |
27 | # Create the charge with Stripe
28 | charge = Stripe::Charge.create({
29 | source: charge_params[:stripeToken],
30 | amount: amount,
31 | currency: "usd",
32 | transfer_data: {
33 | destination: account_id,
34 | },
35 | application_fee_amount: amount/10, # Take a 10% application fee for the platform
36 | metadata: { "name" => charge_params[:name], "campaign" => campaign.id }
37 | }
38 | )
39 |
40 | # Save the charge details locally for later use
41 | local_charge = Charge.create(
42 | charge_id: charge.id,
43 | amount: amount,
44 | name: charge_params[:name],
45 | campaign_id: campaign.id
46 | )
47 |
48 | # Update the amount raised for this campaign
49 | campaign.raised = campaign.raised.to_i + amount
50 | campaign.save
51 |
52 | # Let the customer know the payment was successful
53 | flash[:success] = "Thanks for your donation! Your transaction ID is #{charge.id}."
54 | redirect_to campaign_path(campaign)
55 |
56 | # Handle exceptions from Stripe
57 | rescue Stripe::StripeError => e
58 | flash[:error] = e.message
59 | redirect_to campaign_path(campaign)
60 |
61 | # Handle other failures
62 | rescue => e
63 | flash[:error] = e.message
64 | redirect_to campaign_path(campaign)
65 | end
66 | end
67 |
68 | def show
69 | begin
70 | # Retrieve the charge from Stripe
71 | @charge = Stripe::Charge.retrieve(id: params[:id], expand: ['application_fee', 'dispute'])
72 |
73 | # Validate that the user should be able to view this charge
74 | check_destination(@charge)
75 |
76 | # Get the campaign from the metadata on the charge object
77 | @campaign = Campaign.find(@charge.metadata.campaign)
78 |
79 | # Handle exceptions from Stripe
80 | rescue Stripe::StripeError => e
81 | flash[:error] = e.message
82 | redirect_to dashboard_path
83 |
84 | # Handle other failures
85 | rescue => e
86 | flash[:error] = e.message
87 | redirect_to dashboard_path
88 | end
89 | end
90 |
91 | def index
92 | end
93 |
94 | # Using destroy action to handle refunds
95 | def destroy
96 | begin
97 | # Retrieve the charge from Stripe
98 | charge = Stripe::Charge.retrieve(params[:id])
99 |
100 | # Validate that the user should be able to view this charge
101 | check_destination(charge)
102 |
103 | # Refund the charge
104 | charge.refund(reverse_transfer: true, refund_application_fee: true)
105 |
106 | # Update the local charge
107 | local_charge = Charge.find_by charge_id: charge.id
108 | local_charge.amount_refunded = charge.amount
109 | local_charge.save
110 |
111 | # Update the amount raised for this campaign
112 | campaign = Campaign.find(local_charge.campaign_id)
113 | campaign.raised = campaign.raised.to_i - charge.amount
114 | campaign.save
115 |
116 | # Let the user know the refund was successful
117 | flash[:success] = "The charge has been refunded."
118 | redirect_to dashboard_path
119 |
120 | # Handle Stripe exceptions
121 | rescue Stripe::StripeError => e
122 | flash.now[:error] = e.message
123 | redirect_to dashboard_path
124 |
125 | # Handle other failures
126 | rescue => e
127 | flash.now[:error] = e.message
128 | redirect_to dashboard_path
129 | end
130 | end
131 |
132 | private
133 | def charge_params
134 | params.permit(:amount, :stripeToken, :name, :campaign, :authenticity_token)
135 | end
136 |
137 | # Charge is retrieved from the platform, so only the destination should have access to it
138 | def check_destination(charge)
139 | # Redirect to the dashboard if the charge doesn't belong to current user
140 | unless charge.destination.eql?(current_user.stripe_account)
141 | flash[:error] = "You don't have access to this charge."
142 | redirect_to dashboard_path
143 | end
144 | end
145 | end
146 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/components.scss:
--------------------------------------------------------------------------------
1 | /* ===========
2 | Buttons
3 | =============*/
4 | .btn {
5 | border-radius: 2px;
6 | padding: 6px 14px;
7 | webkit-box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4);
8 | }
9 | .btn-group-lg > .btn,
10 | .btn-lg {
11 | padding: 10px 16px !important;
12 | font-size: 16px;
13 | }
14 | .btn-group-sm > .btn,
15 | .btn-sm {
16 | padding: 5px 10px !important;
17 | }
18 | .btn-group-xs > .btn,
19 | .btn-xs {
20 | padding: 1px 5px !important;
21 | }
22 | .btn-group .btn + .btn,
23 | .btn-group .btn + .btn-group,
24 | .btn-group .btn-group + .btn,
25 | .btn-group .btn-group + .btn-group {
26 | margin-left: 0px;
27 | }
28 | .btn-group.open .dropdown-toggle {
29 | -webkit-box-shadow: 0 0 0 100px rgba(0, 0, 0, 0.1) inset;
30 | box-shadow: 0 0 0 100px rgba(0, 0, 0, 0.1) inset;
31 | }
32 | .btn-primary,
33 | .btn-success,
34 | .btn-info,
35 | .btn-warning,
36 | .btn-danger,
37 | .btn-inverse,
38 | .btn-purple,
39 | .btn-pink {
40 | color: #ffffff !important;
41 | }
42 | .btn-default {
43 | background-color: #dae6ec;
44 | border-color: #dae6ec;
45 | }
46 | .btn-default:focus {
47 | background-color: #dae6ec;
48 | border-color: #C2CED4;
49 | }
50 | .btn-default:hover {
51 | background-color: #dae6ec;
52 | border-color: #C2CED4;
53 | }
54 | .btn-default:active {
55 | background-color: #dae6ec;
56 | border-color: #C2CED4;
57 | }
58 | .btn-default.active,
59 | .btn-default:active,
60 | .open > .dropdown-toggle.btn-default {
61 | background-color: #dae6ec !important;
62 | border-color: #C2CED4 !important;
63 | }
64 | .btn-primary {
65 | background-color: #3bafda !important;
66 | border: 1px solid #3bafda !important;
67 | }
68 | .btn-primary:hover,
69 | .btn-primary:focus,
70 | .btn-primary:active,
71 | .btn-primary.active,
72 | .btn-primary.focus,
73 | .btn-primary:active,
74 | .btn-primary:focus,
75 | .btn-primary:hover,
76 | .open > .dropdown-toggle.btn-primary {
77 | background-color: #28a5d4 !important;
78 | border: 1px solid #28a5d4 !important;
79 | }
80 | .btn-success {
81 | background-color: #00b19d !important;
82 | border: 1px solid #00b19d !important;
83 | }
84 | .btn-success:hover,
85 | .btn-success:focus,
86 | .btn-success:active,
87 | .btn-success.active,
88 | .btn-success.focus,
89 | .btn-success:active,
90 | .btn-success:focus,
91 | .btn-success:hover,
92 | .open > .dropdown-toggle.btn-success {
93 | background-color: #009886 !important;
94 | border: 1px solid #009886 !important;
95 | }
96 | .btn-info {
97 | background-color: #3ddcf7 !important;
98 | border: 1px solid #3ddcf7 !important;
99 | }
100 | .btn-info:hover,
101 | .btn-info:focus,
102 | .btn-info:active,
103 | .btn-info.active,
104 | .btn-info.focus,
105 | .btn-info:active,
106 | .btn-info:focus,
107 | .btn-info:hover,
108 | .open > .dropdown-toggle.btn-info {
109 | background-color: #25d8f6 !important;
110 | border: 1px solid #25d8f6 !important;
111 | }
112 | .btn-warning {
113 | background-color: #ffaa00 !important;
114 | border: 1px solid #ffaa00 !important;
115 | }
116 | .btn-warning:hover,
117 | .btn-warning:focus,
118 | .btn-warning:active,
119 | .btn-warning.active,
120 | .btn-warning.focus,
121 | .btn-warning:active,
122 | .btn-warning:focus,
123 | .btn-warning:hover,
124 | .open > .dropdown-toggle.btn-warning {
125 | background-color: #e69900 !important;
126 | border: 1px solid #e69900 !important;
127 | }
128 | .btn-danger {
129 | background-color: #ef5350 !important;
130 | border: 1px solid #ef5350 !important;
131 | }
132 | .btn-danger:active,
133 | .btn-danger:focus,
134 | .btn-danger:hover,
135 | .btn-danger.active,
136 | .btn-danger.focus,
137 | .btn-danger:active,
138 | .btn-danger:focus,
139 | .btn-danger:hover,
140 | .open > .dropdown-toggle.btn-danger {
141 | background-color: #ed3c39 !important;
142 | border: 1px solid #ed3c39 !important;
143 | }
144 | .btn-inverse {
145 | background-color: #4c5667 !important;
146 | border: 1px solid #4c5667 !important;
147 | }
148 | .btn-inverse:hover,
149 | .btn-inverse:focus,
150 | .btn-inverse:active,
151 | .btn-inverse.active,
152 | .btn-inverse.focus,
153 | .btn-inverse:active,
154 | .btn-inverse:focus,
155 | .btn-inverse:hover,
156 | .open > .dropdown-toggle.btn-inverse {
157 | background-color: #414a58 !important;
158 | border: 1px solid #414a58 !important;
159 | }
160 | .btn-purple {
161 | background-color: #7266ba !important;
162 | border: 1px solid #7266ba !important;
163 | }
164 | .btn-purple:hover,
165 | .btn-purple:focus,
166 | .btn-purple:active {
167 | background-color: #6254b2 !important;
168 | border: 1px solid #6254b2 !important;
169 | }
170 | .btn-pink {
171 | background-color: #f76397 !important;
172 | border: 1px solid #f76397 !important;
173 | }
174 | .btn-pink:hover,
175 | .btn-pink:focus,
176 | .btn-pink:active {
177 | background-color: #f64b87 !important;
178 | border: 1px solid #f64b87 !important;
179 | }
180 | .btn-custom {
181 | box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4);
182 | -webkit-transition: all 0.4s;
183 | -o-transition: all 0.4s;
184 | transition: all 0.4s;
185 | }
186 | .btn-rounded {
187 | border-radius: 2em;
188 | padding: 6px 18px;
189 | }
190 | .fileupload {
191 | overflow: hidden;
192 | position: relative;
193 | }
194 | .fileupload input.upload {
195 | cursor: pointer;
196 | filter: alpha(opacity=0);
197 | font-size: 20px;
198 | margin: 0;
199 | opacity: 0;
200 | padding: 0;
201 | position: absolute;
202 | right: 0;
203 | top: 0;
204 | }
205 |
--------------------------------------------------------------------------------
/app/controllers/campaigns_controller.rb:
--------------------------------------------------------------------------------
1 | class CampaignsController < ApplicationController
2 | before_action :authenticate_user!, except: [:home, :show]
3 | include CampaignsHelper
4 |
5 | def home
6 | # Retrieve all active campaigns
7 | @campaigns = Campaign.where(active: true).order(created_at: :desc).page params[:page]
8 | end
9 |
10 | def new
11 | # Redirect if no existing Stripe account
12 | unless current_user.stripe_account
13 | redirect_to new_stripe_account_path and return
14 | end
15 |
16 | # Populate random campaign info
17 | random_campaign
18 |
19 | # Create a new campaign object
20 | @campaign = Campaign.new
21 | end
22 |
23 | def create
24 | # Create a campaign for the user
25 | @campaign = current_user.campaigns.new(campaign_params)
26 |
27 | # Redirect to the campaign page once created
28 | if @campaign.save
29 | flash[:notice] = "Your campaign has been created!"
30 | redirect_to @campaign
31 | else
32 | handle_error(@campaign.errors.full_messages, 'new')
33 | end
34 | end
35 |
36 | def show
37 | # Retrieve a campaign
38 | @campaign = Campaign.find(params[:id])
39 |
40 | # List all charges for a given campaign
41 | @charges = Charge.where(campaign_id: @campaign.id, amount_refunded: nil).order(created_at: :desc)
42 | end
43 |
44 | def dashboard
45 | # List campaigns for the current user
46 | @campaigns = current_user.campaigns.order(created_at: :desc)
47 |
48 | # Redirect if there's not a Stripe account for this user yet
49 | unless current_user.stripe_account
50 | flash[:success] = "Create an account to get started."
51 | redirect_to new_stripe_account_path and return
52 | end
53 |
54 | # Retrieve charges, transfers, balance transactions, & balance from Stripe
55 | begin
56 | @stripe_account = Stripe::Account.retrieve(current_user.stripe_account)
57 |
58 | # Last 100 charges
59 | @payments = Stripe::Charge.list(
60 | {
61 | limit: 100,
62 | expand: ['data.source_transfer.source_transaction.dispute', 'data.application_fee'],
63 | source: {object: 'all'}
64 | },
65 | { stripe_account: current_user.stripe_account }
66 | )
67 |
68 | # Last 100 payouts from the managed account to their bank account
69 | @payouts = Stripe::Payout.list(
70 | {
71 | limit: 100,
72 | expand: ['data.destination']
73 | },
74 | { stripe_account: current_user.stripe_account }
75 | )
76 |
77 | # Retrieve available and pending balance for an account
78 | @balance = Stripe::Balance.retrieve(stripe_account: current_user.stripe_account)
79 | @balance_available = @balance.available.first.amount + @balance.pending.first.amount
80 |
81 | # Retrieve transactions with an available_on date in the future
82 | # For a large platform, it's generally preferrable to handle these async
83 | transactions = Stripe::BalanceTransaction.all(
84 | {
85 | limit: 100,
86 | available_on: {gte: Time.now.to_i}
87 | },
88 | { stripe_account: current_user.stripe_account }
89 | )
90 |
91 | # Iterate through transactions and sum values for each available_on date
92 | # For a production app, you'll probably want to store and query these locally instead
93 | balances = Hash.new
94 | transactions.auto_paging_each do |txn|
95 | if balances.key?(txn.available_on)
96 | balances[txn.available_on] += txn.net
97 | else
98 | balances[txn.available_on] = txn.net
99 | end
100 | end
101 |
102 | # Sort the results
103 | @transactions = balances.sort_by {|date,net| date}
104 |
105 | # Check for a debit card external account and determine amount for payout
106 | @debit_card = @stripe_account.external_accounts.find { |c| c.object == "card"}
107 | @instant_amt = @balance_available*0.97
108 | @instant_fee = @balance_available*0.03
109 |
110 | # Handle Stripe exceptions
111 | rescue Stripe::StripeError => e
112 | flash[:error] = e.message
113 | redirect_to root_path
114 |
115 | # Handle other exceptions
116 | rescue => e
117 | flash[:error] = e.message
118 | redirect_to root_path
119 | end
120 | end
121 |
122 | def edit
123 | # Retrieve the campaign
124 | @campaign = Campaign.find(params[:id])
125 | end
126 |
127 | def update
128 | # Retrieve the campaign
129 | @campaign = Campaign.find(params[:id])
130 |
131 | # Redirect to view campaign
132 | if @campaign.update_attributes(campaign_params)
133 | flash[:notice] = "Your campaign has been updated!"
134 | redirect_to @campaign
135 | else
136 | handle_error(@campaign.errors.full_messages, 'edit')
137 | end
138 | end
139 |
140 | def destroy
141 | # Retrieve the campaign
142 | campaign = Campaign.find(params[:id])
143 |
144 | # Respond with deletion status
145 | if campaign.update_attributes(active: false)
146 | flash[:notice] = "Your campaign has been deleted."
147 | redirect_to dashboard_path
148 | else
149 | flash[:error] = "We weren't able to delete this campaign."
150 | redirect_to dashboard_path
151 | end
152 | end
153 |
154 | private
155 | def campaign_params
156 | params.require(:campaign).permit(:title, :description, :goal, :subscription, :image)
157 | end
158 |
159 | def random_campaign
160 | data = campaign_data.sample
161 | @campaign_title = data[:title]
162 | @campaign_description = data[:description]
163 | @campaign_image = data[:image]
164 | @goal = amounts.sample
165 | end
166 | end
167 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/custom.scss:
--------------------------------------------------------------------------------
1 | @import "bootstrap-sprockets";
2 | @import "bootstrap";
3 | @import url(https://fonts.googleapis.com/css?family=Lato:400,300);
4 | html {
5 | position: relative;
6 | min-height: 100%;
7 | }
8 | body {
9 | padding-top: 60px;
10 | font-family: "Lato";
11 | font-size: 16px;
12 | margin-bottom: 220px;
13 | background-color: #F6F9FC;
14 | }
15 | h1, h2, h3, h4 {
16 | font-family: "Lato";
17 | font-weight: 300;
18 | }
19 | .footer {
20 | position: absolute;
21 | bottom: 0;
22 | width: 100%;
23 | height: 200px;
24 | padding-top: 40px;
25 | background-color: #272727;
26 | color: #fff;
27 | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
28 | a {
29 | color: #2494be;
30 | }
31 | a:hover {
32 | text-decoration: none;
33 | color: #fff;
34 | }
35 | }
36 | .lato {
37 | font-family: "Lato";
38 | font-weight: 300;
39 |
40 | }
41 | .navbar-default {
42 | background-color: #fff;
43 | height: 60px;
44 | img {
45 | margin-top:10px;
46 | }
47 | .navbar-nav > .active > a, .navbar-nav > .active > a:hover, .navbar-nav > .active > a:focus {
48 | background-color: #fff;
49 | color: #000;
50 | }
51 | .navbar-collapse {
52 | background-color: #fff;
53 | }
54 | .fa {
55 | margin-right: 8px;
56 | }
57 | }
58 | .home-hero {
59 | background-image:url('https://unsplash.it/1200/600?image=0&blur');
60 | background-size:cover;
61 | background-color: #666;
62 | color: #fff;
63 | height: 600px;
64 | h1 {
65 | font-size: 72px;
66 | }
67 | h3 {
68 | color: #f6f6f6;
69 | a {
70 | color: #fff;
71 | font-weight: bold;
72 | }
73 | }
74 | }
75 | .campaign {
76 | font-family: "Lato", Helvetica, Arial, sans-serif!important;
77 | font-weight: 300;
78 | transition: box-shadow .3s;
79 | h2 {
80 | margin-top: 0px;
81 | }
82 | a {
83 | color: #555;
84 | text-decoration: none;
85 | }
86 | a:hover {
87 | color: #7266ba;
88 | }
89 | img {
90 | width: 100%;
91 | }
92 | .panel-body {
93 | padding: 0px;
94 | }
95 | }
96 | .campaign:hover {
97 | webkit-box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
98 | -moz-box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
99 | box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
100 | }
101 | .campaign-content {
102 | img {
103 | width: 100%;
104 | webkit-box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
105 | -moz-box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
106 | box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
107 | }
108 | }
109 | .img-selected {
110 | border: 1px solid #0080FF;
111 | }
112 | .raised {
113 | color: #00b19d;
114 | }
115 | .feature {
116 | font-family: "Lato";
117 | font-weight: 300;
118 | font-size: 26px;
119 | h2 {
120 | color: #4a4a4a;
121 | font-family: "Lato";
122 | font-weight: 300;
123 | }
124 | p {
125 | font-size: 24px;
126 | font-weight: 300;
127 | color: #999;
128 | font-family: sans-serif;
129 | }
130 | .price {
131 | font-size: 200px
132 | }
133 | .price-sm {
134 | font-size: 100px;
135 | }
136 | }
137 | .icon-lg {
138 | font-size: 100px;
139 | }
140 | .icon-huge {
141 | font-size:200px;
142 | }
143 | .topspace {
144 | margin-top: 10px;
145 | }
146 | .topspace-lg {
147 | margin-top: 50px;
148 | }
149 | .leftspace {
150 | margin-left: 8px;
151 | }
152 | .body-padding {
153 | padding: 15px;
154 | }
155 | .red {
156 | color: #B20000;
157 | }
158 | .gray {
159 | color: #666;
160 | }
161 | .pink {
162 | color: #D80A84;
163 | }
164 | .blue {
165 | color: #0080FF;
166 | }
167 | .purple {
168 | color: #7266ba;
169 | }
170 | .white {
171 | color: #fff;
172 | }
173 | .shadow-tiny {
174 | webkit-box-shadow: 0 2px 0 rgba(37,46,53,0.06);
175 | -moz-box-shadow: 0 2px 0 rgba(37,46,53,0.06);
176 | box-shadow: 0 2px 0 rgba(37,46,53,0.06);
177 | }
178 | .shadow-sm {
179 | webkit-box-shadow: 2px 2px 2px rgba(37,46,53,0.06);
180 | -moz-box-shadow: 2px 2px 2px rgba(37,46,53,0.06);
181 | box-shadow: 2px 2px 2px rgba(37,46,53,0.06);
182 | }
183 | .field_with_errors {
184 | @extend .has-error !optional;
185 | display: inline;
186 | }
187 | .highlight {
188 | background-color: #CCE6FF;
189 | }
190 | /* flash */
191 | .alert-alert {
192 | background-color: #ef5350;
193 | border-color: #eed3d7;
194 | color: #fff;
195 | text-align: left;
196 | }
197 | .alert-success {
198 | background-color: #00b19d;
199 | border-color: #d6e9c6;
200 | color: #fff;
201 | text-align: left;
202 | }
203 | .alert-notice {
204 | background-color: #00b19d;
205 | border-color: #d6e9c6;
206 | color: #fff;
207 | text-align: left;
208 | }
209 | .alert-danger h2, .alert-error h2 {
210 | margin-top:0px;
211 | }
212 | .box-shadow {
213 | -webkit-box-shadow: 0px 2px 3px 0px rgba(50, 50, 50, 0.75);
214 | -moz-box-shadow: 0px 2px 3px 0px rgba(50, 50, 50, 0.75);
215 | box-shadow: 0px 2px 3px 0px rgba(50, 50, 50, 0.75);
216 | }
217 | .btn-xl {
218 | font-size: 30px;
219 | padding: 15px;
220 | }
221 | .placeholder label {
222 | display: none !important;
223 | }
224 | .form-control:focus, .StripeElement--focus {
225 | border-color: #939393;
226 | box-shadow: none;
227 | }
228 | .StripeElement--invalid {
229 | border-color: #a94442;
230 | box-shadow: none;
231 | }
232 | .form-control, .has-error .form-control:focus {
233 | border-width: 1px;
234 | box-shadow: none;
235 | }
236 | .panel-payment {
237 | webkit-box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
238 | -moz-box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
239 | box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1),0 5px 15px rgba(0, 0, 0, 0.07);
240 | ::-webkit-input-placeholder, input {
241 | font-size: 15px!important;
242 | color: #666;
243 | font-family: "Open Sans", sans-serif;
244 | -webkit-font-smoothing: antialiased;
245 | -moz-osx-font-smoothing: grayscale;
246 | }
247 | input {
248 | color: #000;
249 | }
250 | }
251 | #card-errors {
252 | display: none;
253 | }
254 |
255 | /* media queries */
256 | @media only screen
257 | and (min-device-width : 375px)
258 | and (max-device-width : 667px) {
259 | .home-hero h1 {
260 | font-size: 60px;
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (5.0.7)
5 | actionpack (= 5.0.7)
6 | nio4r (>= 1.2, < 3.0)
7 | websocket-driver (~> 0.6.1)
8 | actionmailer (5.0.7)
9 | actionpack (= 5.0.7)
10 | actionview (= 5.0.7)
11 | activejob (= 5.0.7)
12 | mail (~> 2.5, >= 2.5.4)
13 | rails-dom-testing (~> 2.0)
14 | actionpack (5.0.7)
15 | actionview (= 5.0.7)
16 | activesupport (= 5.0.7)
17 | rack (~> 2.0)
18 | rack-test (~> 0.6.3)
19 | rails-dom-testing (~> 2.0)
20 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
21 | actionview (5.0.7)
22 | activesupport (= 5.0.7)
23 | builder (~> 3.1)
24 | erubis (~> 2.7.0)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
27 | activejob (5.0.7)
28 | activesupport (= 5.0.7)
29 | globalid (>= 0.3.6)
30 | activemodel (5.0.7)
31 | activesupport (= 5.0.7)
32 | activerecord (5.0.7)
33 | activemodel (= 5.0.7)
34 | activesupport (= 5.0.7)
35 | arel (~> 7.0)
36 | activesupport (5.0.7)
37 | concurrent-ruby (~> 1.0, >= 1.0.2)
38 | i18n (>= 0.7, < 2)
39 | minitest (~> 5.1)
40 | tzinfo (~> 1.1)
41 | arel (7.1.4)
42 | autoprefixer-rails (8.5.0)
43 | execjs
44 | bcrypt (3.1.12)
45 | bindex (0.5.0)
46 | bootstrap-sass (3.3.7)
47 | autoprefixer-rails (>= 5.2.1)
48 | sass (>= 3.3.4)
49 | builder (3.2.3)
50 | byebug (10.0.2)
51 | coffee-rails (4.2.2)
52 | coffee-script (>= 2.2.0)
53 | railties (>= 4.0.0)
54 | coffee-script (2.4.1)
55 | coffee-script-source
56 | execjs
57 | coffee-script-source (1.12.2)
58 | concurrent-ruby (1.0.5)
59 | crass (1.0.4)
60 | data-confirm-modal (1.6.2)
61 | railties (>= 3.0)
62 | devise (4.4.3)
63 | bcrypt (~> 3.0)
64 | orm_adapter (~> 0.1)
65 | railties (>= 4.1.0, < 6.0)
66 | responders
67 | warden (~> 1.2.3)
68 | domain_name (0.5.20180417)
69 | unf (>= 0.0.5, < 1.0.0)
70 | erubis (2.7.0)
71 | execjs (2.7.0)
72 | faraday (0.15.1)
73 | multipart-post (>= 1.2, < 3)
74 | ffi (1.9.23)
75 | font-awesome-rails (4.7.0.4)
76 | railties (>= 3.2, < 6.0)
77 | globalid (0.4.1)
78 | activesupport (>= 4.2.0)
79 | http-cookie (1.0.3)
80 | domain_name (~> 0.5)
81 | i18n (1.0.1)
82 | concurrent-ruby (~> 1.0)
83 | jbuilder (2.7.0)
84 | activesupport (>= 4.2.0)
85 | multi_json (>= 1.2)
86 | jquery-rails (4.3.3)
87 | rails-dom-testing (>= 1, < 3)
88 | railties (>= 4.2.0)
89 | thor (>= 0.14, < 2.0)
90 | kaminari (1.1.1)
91 | activesupport (>= 4.1.0)
92 | kaminari-actionview (= 1.1.1)
93 | kaminari-activerecord (= 1.1.1)
94 | kaminari-core (= 1.1.1)
95 | kaminari-actionview (1.1.1)
96 | actionview
97 | kaminari-core (= 1.1.1)
98 | kaminari-activerecord (1.1.1)
99 | activerecord
100 | kaminari-core (= 1.1.1)
101 | kaminari-core (1.1.1)
102 | listen (3.0.8)
103 | rb-fsevent (~> 0.9, >= 0.9.4)
104 | rb-inotify (~> 0.9, >= 0.9.7)
105 | loofah (2.2.2)
106 | crass (~> 1.0.2)
107 | nokogiri (>= 1.5.9)
108 | mail (2.7.0)
109 | mini_mime (>= 0.1.1)
110 | mailgun-ruby (1.1.9)
111 | rest-client (~> 2.0)
112 | method_source (0.9.0)
113 | mime-types (3.1)
114 | mime-types-data (~> 3.2015)
115 | mime-types-data (3.2016.0521)
116 | mini_mime (1.0.0)
117 | mini_portile2 (2.3.0)
118 | minitest (5.11.3)
119 | multi_json (1.13.1)
120 | multipart-post (2.0.0)
121 | netrc (0.11.0)
122 | nio4r (2.3.1)
123 | nokogiri (1.8.2)
124 | mini_portile2 (~> 2.3.0)
125 | orm_adapter (0.5.0)
126 | pg (0.18.4)
127 | puma (3.11.4)
128 | rack (2.0.5)
129 | rack-test (0.6.3)
130 | rack (>= 1.0)
131 | rails (5.0.7)
132 | actioncable (= 5.0.7)
133 | actionmailer (= 5.0.7)
134 | actionpack (= 5.0.7)
135 | actionview (= 5.0.7)
136 | activejob (= 5.0.7)
137 | activemodel (= 5.0.7)
138 | activerecord (= 5.0.7)
139 | activesupport (= 5.0.7)
140 | bundler (>= 1.3.0)
141 | railties (= 5.0.7)
142 | sprockets-rails (>= 2.0.0)
143 | rails-dom-testing (2.0.3)
144 | activesupport (>= 4.2.0)
145 | nokogiri (>= 1.6)
146 | rails-html-sanitizer (1.0.4)
147 | loofah (~> 2.2, >= 2.2.2)
148 | railties (5.0.7)
149 | actionpack (= 5.0.7)
150 | activesupport (= 5.0.7)
151 | method_source
152 | rake (>= 0.8.7)
153 | thor (>= 0.18.1, < 2.0)
154 | rake (12.3.1)
155 | rb-fsevent (0.10.3)
156 | rb-inotify (0.9.10)
157 | ffi (>= 0.5.0, < 2)
158 | responders (2.4.0)
159 | actionpack (>= 4.2.0, < 5.3)
160 | railties (>= 4.2.0, < 5.3)
161 | rest-client (2.0.2)
162 | http-cookie (>= 1.0.2, < 2.0)
163 | mime-types (>= 1.16, < 4.0)
164 | netrc (~> 0.8)
165 | sass (3.5.6)
166 | sass-listen (~> 4.0.0)
167 | sass-listen (4.0.0)
168 | rb-fsevent (~> 0.9, >= 0.9.4)
169 | rb-inotify (~> 0.9, >= 0.9.7)
170 | sass-rails (5.0.7)
171 | railties (>= 4.0.0, < 6)
172 | sass (~> 3.1)
173 | sprockets (>= 2.8, < 4.0)
174 | sprockets-rails (>= 2.0, < 4.0)
175 | tilt (>= 1.1, < 3)
176 | spring (2.0.2)
177 | activesupport (>= 4.2)
178 | spring-watcher-listen (2.0.1)
179 | listen (>= 2.7, < 4.0)
180 | spring (>= 1.2, < 3.0)
181 | sprockets (3.7.1)
182 | concurrent-ruby (~> 1.0)
183 | rack (> 1, < 3)
184 | sprockets-rails (3.2.1)
185 | actionpack (>= 4.0)
186 | activesupport (>= 4.0)
187 | sprockets (>= 3.0.0)
188 | sqlite3 (1.3.13)
189 | stripe (3.15.0)
190 | faraday (~> 0.10)
191 | thor (0.20.0)
192 | thread_safe (0.3.6)
193 | tilt (2.0.8)
194 | tzinfo (1.2.5)
195 | thread_safe (~> 0.1)
196 | uglifier (4.1.10)
197 | execjs (>= 0.3.0, < 3)
198 | unf (0.1.4)
199 | unf_ext
200 | unf_ext (0.0.7.5)
201 | warden (1.2.7)
202 | rack (>= 1.0)
203 | web-console (3.6.2)
204 | actionview (>= 5.0)
205 | activemodel (>= 5.0)
206 | bindex (>= 0.4.0)
207 | railties (>= 5.0)
208 | websocket-driver (0.6.5)
209 | websocket-extensions (>= 0.1.0)
210 | websocket-extensions (0.1.3)
211 |
212 | PLATFORMS
213 | ruby
214 |
215 | DEPENDENCIES
216 | bootstrap-sass (~> 3.3.6)
217 | byebug
218 | coffee-rails (~> 4.2)
219 | data-confirm-modal
220 | devise (~> 4.2, >= 4.2.1)
221 | font-awesome-rails
222 | jbuilder (~> 2.5)
223 | jquery-rails
224 | kaminari
225 | listen (~> 3.0.5)
226 | mailgun-ruby
227 | pg (~> 0.18.4)
228 | puma (~> 3.0)
229 | rails (~> 5.0.0, >= 5.0.0.1)
230 | sass-rails (~> 5.0)
231 | spring
232 | spring-watcher-listen (~> 2.0.0)
233 | sqlite3
234 | stripe
235 | tzinfo-data
236 | uglifier (>= 1.3.0)
237 | web-console
238 |
239 | BUNDLED WITH
240 | 1.16.1
241 |
--------------------------------------------------------------------------------
/app/controllers/stripe_accounts_controller.rb:
--------------------------------------------------------------------------------
1 | class StripeAccountsController < ApplicationController
2 | before_action :authenticate_user!
3 |
4 | def new
5 | @account = StripeAccount.new
6 | end
7 |
8 | def create
9 | @account = StripeAccount.new(account_params)
10 |
11 | if @account.save
12 | begin
13 | # There are different requirements for individuals vs companies
14 | # Using separate request for each in this example
15 |
16 | # If this is an individual, send request params to create an individual acct
17 | if account_params[:account_type].eql?('individual')
18 | stripe_account = Stripe::Account.create(
19 | type: 'custom',
20 | requested_capabilities: ['platform_payments'], # Donors interact with the platform
21 | business_type: 'individual',
22 | individual: {
23 | first_name: account_params[:first_name].capitalize,
24 | last_name: account_params[:last_name].capitalize,
25 | dob: {
26 | day: account_params[:dob_day],
27 | month: account_params[:dob_month],
28 | year: account_params[:dob_year],
29 | },
30 | ssn_last_4: account_params[:ssn_last_4],
31 | },
32 | business_profile: {
33 | product_description: 'Fundraising campaign',
34 | },
35 | tos_acceptance: {
36 | date: Time.now.to_i,
37 | ip: request.remote_ip
38 | }
39 | )
40 |
41 | # If the account type is a company, send company information
42 | else
43 | stripe_account = Stripe::Account.create(
44 | type: 'custom',
45 | requested_capabilities: ['platform_payments'], # Donors interact with the platform
46 | business_type: 'company',
47 | company: {
48 | name: account_params[:business_name],
49 | tax_id: account_params[:business_tax_id],
50 | },
51 | business_profile: {
52 | product_description: 'Fundraising campaign',
53 | },
54 | tos_acceptance: {
55 | date: Time.now.to_i,
56 | ip: request.remote_ip
57 | }
58 | )
59 | end
60 |
61 | # Save the account ID for this user for later
62 | @account.acct_id = stripe_account.id
63 | @account.save
64 | current_user.stripe_account = stripe_account.id
65 |
66 | if current_user.save
67 | flash[:success] = "Your account has been created!
68 | Next, add a bank account where you'd like to receive transfers below."
69 | redirect_to new_bank_account_path
70 | else
71 | handle_error("Sorry, we weren't able to create this account.", 'new')
72 | end
73 |
74 | # Handle exceptions from Stripe
75 | rescue Stripe::StripeError => e
76 | handle_error(e.message, 'new')
77 |
78 | # Handle any other exceptions
79 | rescue => e
80 | handle_error(e.message, 'new')
81 | end
82 | else
83 | @full_account = true if params[:full_account]
84 | handle_error(@account.errors.full_messages)
85 | end
86 | end
87 |
88 | def edit
89 | # Check for a valid account ID
90 | unless params[:id] && params[:id].eql?(current_user.stripe_account)
91 | flash[:error] = "No Stripe account specified"
92 | redirect_to dashboard_path and return
93 | end
94 |
95 | # Retrieve the Stripe account to find fields needed
96 | @stripe_account = Stripe::Account.retrieve(params[:id])
97 |
98 | # Retrieve the local account details
99 | @account = StripeAccount.find_by(acct_id: params[:id])
100 |
101 | if @stripe_account.requirements.currently_due.empty?
102 | flash[:success] = "Your information is all up to date."
103 | redirect_to dashboard_path and return
104 | end
105 | end
106 |
107 | def update
108 | # Check for an existing Stripe account
109 | unless current_user.stripe_account
110 | redirect_to new_stripe_account_path and return
111 | end
112 |
113 | begin
114 | # Retrieve the Stripe account
115 | @stripe_account = Stripe::Account.retrieve(current_user.stripe_account)
116 |
117 | @account = StripeAccount.new(account_params)
118 |
119 |
120 | # Reject empty values
121 | account_params.each do |key, value|
122 | if value.empty?
123 | flash.now[:alert] = "Please complete all fields."
124 | render 'edit' and return
125 | end
126 | end
127 |
128 | # Iterate through each field needed
129 | @stripe_account.requirements.eventually_due.each do |field|
130 |
131 | # Update each needed attribute
132 | case field
133 | when 'individual.address.city'
134 | @stripe_account.individual.address.city = account_params[:address_city]
135 | when 'individual.address.line1'
136 | @stripe_account.individual.address.line1 = account_params[:address_line1]
137 | when 'individual.address.postal_code'
138 | @stripe_account.individual.address.postal_code = account_params[:address_postal]
139 | when 'individual.address.state'
140 | @stripe_account.individual.address.state = account_params[:address_state]
141 | when 'individual.dob.day'
142 | @stripe_account.individual.dob.day = account_params[:dob_day]
143 | when 'individual.dob.month'
144 | @stripe_account.individual.dob.month = account_params[:dob_month]
145 | when 'individual.dob.year'
146 | @stripe_account.individual.dob.year = account_params[:dob_year]
147 | when 'individual.first_name'
148 | @stripe_account.individual.first_name = account_params[:first_name]
149 | when 'individual.last_name'
150 | @stripe_account.individual.last_name = account_params[:last_name]
151 | when 'individual.ssn_last_4'
152 | @stripe_account.individual.ssn_last_4 = account_params[:ssn_last_4]
153 | when 'business_type'
154 | @stripe_account.business_type = account_params[:type]
155 | when 'individual.id_number'
156 | @stripe_account.individual.id_number = account_params[:personal_id_number]
157 | when 'individual.verification.document'
158 | @stripe_account.individual.verification.document.front = account_params[:verification_document]
159 | when 'company.name'
160 | @stripe_account.company.name = account_params[:business_name]
161 | when 'company.tax_id'
162 | @stripe_account.company.tax_id = account_params[:business_tax_id]
163 | end
164 | end
165 |
166 | @stripe_account.save
167 | flash[:success] = "Thanks! Your account has been updated."
168 | redirect_to dashboard_path and return
169 |
170 | # Handle exceptions from Stripe
171 | rescue Stripe::StripeError => e
172 | handle_error(e.message, 'edit')
173 |
174 | # Handle any other exceptions
175 | rescue => e
176 | handle_error(e.message, 'edit')
177 | end
178 | end
179 |
180 | private
181 | def account_params
182 | params.require(:stripe_account).permit(
183 | :first_name, :last_name, :account_type, :dob_month, :dob_day, :dob_year, :tos,
184 | :ssn_last_4, :address_line1, :address_city, :address_state, :address_postal, :business_name,
185 | :business_tax_id, :full_account, :personal_id_number, :verification_document
186 | )
187 | end
188 | end
189 |
--------------------------------------------------------------------------------
/app/views/campaigns/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= content_for :page_title, "#{@campaign.title.titleize} | Fundraising Marketplace" %>
2 | <% content_for(:header) do %>
3 | <% if params[:checkout].present? %>
4 |
5 |
49 | <% else %>
50 |
51 |
142 | <% end %>
143 | <% end %>
144 |
145 |
146 |
147 | <%= render 'layouts/messages' %>
148 | <% unless @campaign.active %>
149 |
150 | This campaign is no longer active.
151 |
152 | <% end %>
153 |
154 | <%= image_tag("#{@campaign.image}_lg.jpg", class: "img-responsive") %>
155 |
156 |
159 |
160 | <% if @campaign.raised %>
161 | <%= format_amount(@campaign.raised) %> raised so far of <%= number_to_currency(@campaign.goal, precision: 0) %> goal
162 | <% else %>
163 | <%= number_to_currency(@campaign.goal) %> goal
164 | <% end %>
165 |
166 |
167 | <%= simple_format(@campaign.description) %>
168 |
169 |
170 | <% unless @campaign.raised.to_i > 0 %>
171 | This campaign has not received any donations yet.
172 | <% else %>
173 | <%= render 'donations' %>
174 | <% end %>
175 |
176 |
177 | <% if @campaign.active %>
178 | <% if params[:checkout].present? %>
179 | <%= render 'checkoutform' %>
180 | <% else %>
181 | <%= render 'paymentform' %>
182 | <% end %>
183 |
184 |
185 | Hint: use a Stripe test card and any valid expiry.
186 |
187 |
188 | <% end %>
189 | <% if current_user && @campaign.user_id.eql?(current_user.id) && @campaign.active %>
190 |
191 |
192 | <%= link_to "Edit this campaign", edit_campaign_path(@campaign),
193 | class: "btn btn-lg btn-primary btn-custom btn-block"%>
194 |
195 |
196 | <%= link_to "Delete this campaign", campaign_path(@campaign), method: :delete, data: { confirm: "Are you sure you want to delete this campaign?" }, class: "btn btn-lg btn-block btn-custom btn-danger" %>
197 |
198 | <% end %>
199 |
200 |
201 |
202 |
--------------------------------------------------------------------------------