module Saucy module Account extend ActiveSupport::Concern included do require "rubygems" require "braintree" CUSTOMER_ATTRIBUTES = { :cardholder_name => :cardholder_name, :billing_email => :email, :card_number => :number, :expiration_month => :expiration_month, :expiration_year => :expiration_year, :verification_code => :cvv } extend ActiveSupport::Memoizable has_many :memberships, :dependent => :destroy has_many :users, :through => :memberships has_many :projects, :dependent => :destroy has_many :admins, :through => :memberships, :source => :user, :conditions => { 'memberships.admin' => true } belongs_to :plan delegate :free?, :billed?, :to => :plan validates_uniqueness_of :name, :keyword validates_presence_of :name, :keyword, :plan_id attr_accessor *CUSTOMER_ATTRIBUTES.keys attr_accessible :name, :keyword validates_format_of :keyword, :with => %r{^[a-z0-9]+$}, :message => "must be only lower case letters." before_create :create_customer before_create :create_subscription, :if => :billed? after_destroy :destroy_customer memoize :customer memoize :subscription end module InstanceMethods def to_param keyword end def has_member?(user) memberships.exists?(:user_id => user.id) end def users_by_name users.by_name end def projects_by_name projects.by_name end def projects_visible_to(user) projects.visible_to(user) end def memberships_by_name memberships.by_name end def customer Braintree::Customer.find(customer_token) if customer_token end def credit_card customer.credit_cards[0] if customer && customer.credit_cards.any? end def subscription Braintree::Subscription.find(subscription_token) if subscription_token end def save_braintree!(attributes) successful = true self.plan = ::Plan.find(attributes[:plan_id]) if changing_plan?(attributes) if changing_braintree_attributes?(attributes) successful = update_braintree_customer(attributes) end if successful && changing_plan?(attributes) save_subscription flush_cache :subscription end successful && save end def can_change_plan_to?(new_plan) plan.limits.where(:value_type => :number).all? do |limit| new_plan.limit(limit.name).value >= send(limit.name).count end end def past_due? subscription_status == Braintree::Subscription::Status::PastDue end private def changing_plan?(attributes) attributes[:plan_id].present? end def changing_braintree_attributes?(attributes) CUSTOMER_ATTRIBUTES.keys.any? { |attribute| attributes[attribute].present? } end def set_braintree_attributes(attributes) CUSTOMER_ATTRIBUTES.keys.each do |attribute| send("#{attribute}=", attributes[attribute]) if attributes[attribute].present? end end def update_braintree_customer(attributes) set_braintree_attributes(attributes) result = Braintree::Customer.update(customer_token, customer_attributes) handle_customer_result(result) result.success? end def save_subscription if subscription Braintree::Subscription.update(subscription_token, :plan_id => plan_id) elsif plan.billed? create_subscription end end def customer_attributes { :email => billing_email, :credit_card => credit_card_attributes } end def credit_card_attributes if plan.billed? card_attributes = { :cardholder_name => cardholder_name, :number => card_number, :expiration_month => expiration_month, :expiration_year => expiration_year, :cvv => verification_code } if credit_card card_attributes.merge!(:options => credit_card_options) end card_attributes else {} end end def credit_card_options if customer && customer.credit_cards.any? { :update_existing_token => credit_card.token } else {} end end def create_customer result = Braintree::Customer.create(customer_attributes) handle_customer_result(result) end def destroy_customer Braintree::Customer.delete(customer_token) end def handle_customer_result(result) if result.success? self.customer_token = result.customer.id flush_cache :customer else verification = result.credit_card_verification if verification && verification.status == "processor_declined" errors[:card_number] << "was denied by the payment processor with the message: #{verification.processor_response_text}" elsif verification && verification.status == "gateway_rejected" errors[:verification_code] << "did not match" elsif result.errors.any? result.errors.each do |error| if error.attribute == "number" errors[:card_number] << error.message.gsub("Credit card number ", "") elsif error.attribute == "CVV" errors[:verification_code] << error.message.gsub("CVV ", "") elsif error.attribute == "expiration_month" errors[:expiration_month] << error.message.gsub("Expiration month ", "") elsif error.attribute == "expiration_year" errors[:expiration_year] << error.message.gsub("Expiration year ", "") end end end end result.success? end def create_subscription result = Braintree::Subscription.create( :payment_method_token => credit_card.token, :plan_id => plan_id ) if result.success? self.subscription_token = result.subscription.id self.next_billing_date = result.subscription.next_billing_date self.subscription_status = result.subscription.status else false end end end module ClassMethods def update_subscriptions! recently_billed = where("next_billing_date <= '#{Time.now}'") recently_billed.each do |account| account.subscription_status = account.subscription.status account.next_billing_date = account.subscription.next_billing_date account.save! if account.past_due? BillingMailer.problem(account, account.subscription.transactions.last).deliver! else BillingMailer.receipt(account, account.subscription.transactions.last).deliver! end end end end end end