Skip to content

Commit

Permalink
Merge branch 'master' of github.com:MayOneUS/mayday-2.0-backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Rio517 committed Feb 8, 2016
2 parents 5c1b4e2 + d948701 commit 3a303b7
Show file tree
Hide file tree
Showing 52 changed files with 1,214 additions and 1,315 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ TWILIO_RECORDING_APP_SID='APddddddddddddddddddddddddddddddddd'
TWILIO_APP_PHONE_NUMBER='555-555-5555'
TWILIO_APP_RECORDING_NUMBER='555-555-5555'
TWILIO_AUDIO_AWS_BUCKET_URL='https://s3.amazonaws.com/whatever/'

STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ bower.json

# Ignore pow environment settings
.powenv

# ignore byebug history
.byebug_history
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'redis-objects'
gem 'redis-rails'
gem 'database_cleaner'
gem 'phony_rails', :require => false
gem 'validates_email_format_of'

# Active Job
gem 'sidekiq'
Expand All @@ -17,6 +18,7 @@ gem 'sinatra' # Used for the sidekiq UI
gem 'rest-client', :require => false
gem 'oauth2'
gem 'twilio-ruby', :require => false
gem 'nationbuilder-rb', require: 'nationbuilder'

# API publishing
gem 'rails-api'
Expand All @@ -33,6 +35,9 @@ end
gem 'unicorn'
gem 'airbrake'

# Payment processing
gem 'stripe'

# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

Expand Down Expand Up @@ -61,5 +66,6 @@ group :test do
gem 'webmock'
gem 'fakeredis', :require => 'fakeredis/rspec'
gem 'timecop'
gem 'shoulda-matchers'
end

14 changes: 14 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ GEM
hirb (0.7.3)
http-cookie (1.0.2)
domain_name (~> 0.5)
httpclient (2.7.1)
i18n (0.7.0)
jbuilder (2.4.0)
activesupport (>= 3.0.0, < 5.1)
Expand All @@ -106,6 +107,8 @@ GEM
multi_json (1.11.2)
multi_xml (0.5.5)
multipart-post (2.0.0)
nationbuilder-rb (1.3.7)
httpclient (~> 2.7)
netrc (0.11.0)
newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.1)
Expand Down Expand Up @@ -211,6 +214,8 @@ GEM
ansi
ast
safe_yaml (1.0.4)
shoulda-matchers (3.0.1)
activesupport (>= 4.0.0)
sidekiq (4.0.1)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
Expand All @@ -235,6 +240,9 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
stripe (1.31.0)
json (~> 1.8.1)
rest-client (~> 1.4)
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.1)
Expand All @@ -253,6 +261,8 @@ GEM
rack
raindrops (~> 0.7)
uniform_notifier (1.9.0)
validates_email_format_of (1.6.3)
i18n
versionist (1.4.1)
activesupport (>= 3)
railties (>= 3)
Expand Down Expand Up @@ -285,6 +295,7 @@ DEPENDENCIES
fakeredis
hirb
jbuilder (~> 2.0)
nationbuilder-rb
newrelic_rpm
oauth2
oga
Expand All @@ -301,13 +312,16 @@ DEPENDENCIES
redis-rails
rest-client
rspec-rails
shoulda-matchers
sidekiq
sinatra
spring
spring-commands-rspec
stripe
timecop
twilio-ruby
unicorn
validates_email_format_of
versionist
web-console (~> 2.0)
webmock
Expand Down
87 changes: 87 additions & 0 deletions app/controllers/v1/donation_pages_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
class V1::DonationPagesController < ApplicationController
def index
@donation_pages = DonationPage.by_funds_raised.limit(limit)

render :index
end

def show
@donation_page = find_donation_page

render :show
end

def create
person = find_or_initialize_person
donation_page = person.donation_pages.new(donation_page_params)

if person.save && donation_page.save
donation_page.reload
render json: { uuid: donation_page.uuid }, status: :created
else
render json: { errors: merge_errors(person, donation_page) },
status: :unprocessable_entity
end
end

def update
donation_page = find_donation_page

if donation_page.authorize_and_update(donation_page_params)
head :no_content
else
render json: { errors: @donation_page.errors },
status: :unprocessable_entity
end
end

def destroy
donation_page = find_donation_page

donation_page.destroy

head :no_content
end

def validate
person = find_or_initialize_person
donation_page = person.donation_pages.new(donation_page_params)

valid = person.valid? # this will also validate person.donation_pages
errors = person.errors.to_hash.merge(donation_page.errors.to_hash)
render json: { valid: valid, errors: errors }
end

private

def merge_errors(*records)
errors = {}
records.each do |record|
errors.merge!(record.errors.to_hash)
end
errors
end

def find_donation_page
DonationPage.find_by!(slug: params[:slug])
end

def donation_page_params
params.require(:donation_page).permit(:title, :slug, :visible_user_name,
:photo_url, :intro_text, :goal_in_cents, :access_token)
end

def person_params
params.require(:person).permit(Person::PERMITTED_PUBLIC_FIELDS)
end

def find_or_initialize_person
person = Person.find_or_initialize_by(email: person_params[:email])
person.assign_attributes(person_params)
person
end

def limit
params[:limit] || 10
end
end
33 changes: 33 additions & 0 deletions app/controllers/v1/donations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class V1::DonationsController < V1::BaseController

# Receives person, payment and action attributes. Creates/updates a person
# creates an action, and creates a stripe single or recurring payment
# Params:
# * email - string - person email
# * employer - string - person employer
# * occupation - string - person occupation
# * amount_in_cents - int - donation amount in cents
# * recurring - true - pass true if recurring donation
# * stripe_token - token returned by Stripe.js to identify credit card
# * template_id - action template_id
# * utm_source - action utm_source
# * utm_medium - action utm_medium
# * utm_campaign - action utm_campaign
# * source_url - action source_url
def create
donation = Donation.new(donation_params)
if donation.process
render json: { status: 'success' }
else
render json: { errors: donation.errors }
end
end

private

def donation_params
params.permit(:email, :employer, :occupation, :stripe_token, :recurring,
:amount_in_cents, :utm_source, :utm_medium, :utm_campaign,
:source_url, :template_id)
end
end
4 changes: 2 additions & 2 deletions app/controllers/v1/people_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
class V1::PeopleController < V1::BaseController

def create
@person = Person.create_or_update(person_params)
if @person.valid?
@person = PersonWithRemoteFields.find_or_build(person_params)
if @person.save
if template_ids = params[:actions].presence
@person.mark_activities_completed(template_ids)
end
Expand Down
12 changes: 12 additions & 0 deletions app/jobs/nb_donation_create_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class NbDonationCreateJob < ActiveJob::Base
queue_as :default

def perform(amount_in_cents, person_attributes)
nb_args = Integration::NationBuilder.person_params(person_attributes)
response = Integration::NationBuilder.create_or_update_person(nb_args) || {}
person_id = response['id']

Integration::NationBuilder.create_donation(amount_in_cents: amount_in_cents,
person_id: person_id)
end
end
1 change: 1 addition & 0 deletions app/jobs/nb_person_push_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def perform(person_attributes)
nb_args = Integration::NationBuilder.person_params(person_attributes)
response = Integration::NationBuilder.create_or_update_person(nb_args) || {}
person_id = response['id']

if event_id.present? && person_id.present?
Integration::NationBuilder.create_rsvp(event_id: event_id, person_id: person_id)
end
Expand Down
1 change: 1 addition & 0 deletions app/models/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
class Action < ActiveRecord::Base
belongs_to :person, required: true
belongs_to :activity, required: true
belongs_to :donation_page

scope :by_type, ->(activity_type) { joins(:activity).where('activities.activity_type' => activity_type) }
scope :by_date, ->(start_at, end_at=nil) { where("created_at >= ? AND created_at <= ?", start_at, end_at || Time.now) }
Expand Down
3 changes: 2 additions & 1 deletion app/models/activity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Activity < ActiveRecord::Base
DEFAULT_TEMPLATE_IDS = {
rsvp: 'rsvp',
call_congress: 'call-congress',
record_message: 'record-message'
record_message: 'record-message',
donate: 'donate'
}
end
94 changes: 94 additions & 0 deletions app/models/donation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
class Donation
include ActiveModel::Model

attr_accessor :email, :employer, :occupation, :stripe_token, :recurring,
:utm_source, :utm_medium, :utm_campaign, :source_url, :amount_in_cents

attr_writer :template_id

validates :email, presence: true, email_format: true
validates :employer, presence: true
validates :occupation, presence: true
validates :stripe_token, presence: true
validates :amount_in_cents, presence: true,
numericality: { greater_than: 0, only_integer: true }

def process
if valid?
find_or_create_person
process_payment
record_donation
create_donate_action
else
false
end

rescue Stripe::CardError => e
errors.add(:stripe_token, e.json_body[:error][:message])
false
end

private

attr_reader :person

def process_payment
if recurring
stripe_customer = StripeCustomer.new(create_stripe_customer)
person.update(stripe_id: stripe_customer.id)
person.create_subscription(remote_id: stripe_customer.subscription_id)
else
create_stripe_charge
end
end

def record_donation
person = { email: email, employer: employer, occupation: occupation }
NbDonationCreateJob.perform_later(amount_as_integer, person)
end

def create_donate_action
person.create_action(utm_source: utm_source,
utm_medium: utm_medium,
utm_campaign: utm_campaign,
source_url: source_url,
template_id: template_id,
donation_amount_in_cents: amount_as_integer)
end

def template_id
@template_id ||= Activity::DEFAULT_TEMPLATE_IDS[:donate]
end

def find_or_create_person
@person = Person.find_or_initialize_by(email: email)
@person.update(skip_nb_update: true)
end

def create_stripe_customer
Stripe::Customer.create(source: stripe_token,
plan: 'one_dollar_monthly',
email: email,
quantity: amount_as_integer/100)
end

def create_stripe_charge
Stripe::Charge.create(amount: amount_as_integer,
source: stripe_token,
currency: 'usd',
description: "donation from #{email}")
end

def amount_as_integer
amount_in_cents.to_i
end

class StripeCustomer
attr_reader :id, :subscription_id

def initialize(stripe_customer)
@id = stripe_customer.id
@subscription_id = stripe_customer.subscriptions.first.id
end
end
end
Loading

0 comments on commit 3a303b7

Please sign in to comment.