diff options
60 files changed, 518 insertions, 798 deletions
| @@ -46,7 +46,7 @@ group :production do                                  # this must not be included in development mode, or js                                  # will get included twice.    gem 'therubyracer', "~> 0.12.2", :platforms => :ruby -  #   ^^ See https://github.com/sstephenson/execjs#readme +  #    ^^ See https://github.com/sstephenson/execjs#readme    #      for list of supported runtimes.  end diff --git a/Gemfile.lock b/Gemfile.lock index 894e062..7f5e21a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,7 +63,7 @@ GEM        debug_inspector (>= 0.0.1)      bootstrap-sass (2.3.2.2)        sass (~> 3.2) -    braintree (2.38.0) +    braintree (2.48.1)        builder (>= 2.0.0)      builder (3.0.4)      capybara (2.4.4) @@ -122,7 +122,7 @@ GEM      factory_girl_rails (4.5.0)        factory_girl (~> 4.5.0)        railties (>= 3.0.0) -    fake_braintree (0.6.0) +    fake_braintree (0.7.0)        activesupport        braintree (~> 2.32)        capybara (>= 2.0.3) @@ -314,3 +314,6 @@ DEPENDENCIES    thin    uglifier (~> 1.2.7)    valid_email + +BUNDLED WITH +   1.10.6 diff --git a/app/models/anonymous_user.rb b/app/models/anonymous_user.rb index 0c1f540..73e95e5 100644 --- a/app/models/anonymous_user.rb +++ b/app/models/anonymous_user.rb @@ -12,6 +12,10 @@ class AnonymousUser < Object    def id      nil    end +   +  def has_payment_info? +    false +  end    def email      nil @@ -32,4 +36,5 @@ class AnonymousUser < Object    def is_anonymous?      true    end +  end diff --git a/app/models/user.rb b/app/models/user.rb index 3daee0f..4bb1e79 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,6 +9,9 @@ class User < CouchRest::Model::Base    property :contact_email, String, :accessible => true    property :contact_email_key, String, :accessible => true    property :invite_code, String, :accessible => true +  property :braintree_customer_id, Integer, :accessible => true +  property :subscription_id, String, :accessible => true +    property :enabled, TrueClass, :default => true    # these will be null by default but we shouldn't ever pull them directly, but only via the methods that will return the full ServiceLevel @@ -177,6 +180,10 @@ class User < CouchRest::Model::Base    end +  def has_payment_info? +    braintree_customer_id +  end +    protected    ## diff --git a/app/views/layouts/_footer.html.haml b/app/views/layouts/_footer.html.haml index fa523d4..3bba21f 100644 --- a/app/views/layouts/_footer.html.haml +++ b/app/views/layouts/_footer.html.haml @@ -15,3 +15,4 @@        = link_to icon('comment') + t(:contact), contact_path      - if paid_service_level?        = link_to icon('shopping-cart') + t(:pricing), pricing_path +    = link_to icon('barcode') + t(:Donations), new_payment_path diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index dccba0c..63a361a 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -7,6 +7,8 @@    = link_to_navigation ".tickets", auto_tickets_path,      active: controller?(:tickets)    - if APP_CONFIG[:billing] -    = link_to_navigation :billing_settings, billing_top_link(@user), -      active: controller?(:customer, :payments, :subscriptions, :credit_card_info) +    = link_to_navigation :donations, new_payment_path, +      active: (controller?(:donations) and action?(:new)) +    = link_to_navigation :subscriptions, billing_top_link(@braintree_customer_id), +      active: controller?(:subscriptions)    = link_to_navigation :logout, logout_path, method: :delete diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 08e1321..32b0f03 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -23,6 +23,7 @@ cs:    pricing: Stanovéní cen    about: O nás    contact: Kontakt +  donations: Donations    signup: Registrovat    login: Přihlásit    logout: Odhlásit diff --git a/config/locales/de.yml b/config/locales/de.yml index 76ecc1d..1f78b88 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -23,6 +23,7 @@ de:    pricing: Kosten    about: über uns    contact: Kontakt +  donations: Spende    signup: Registrieren    login: Anmelden    logout: Abmelden diff --git a/config/locales/en/footer.en.yml b/config/locales/en/footer.en.yml index 65f8ab2..7569070 100644 --- a/config/locales/en/footer.en.yml +++ b/config/locales/en/footer.en.yml @@ -5,3 +5,4 @@ en:    pricing: Pricing    about: About Us    contact: Contact +  donations: Donations diff --git a/config/locales/en/generic.en.yml b/config/locales/en/generic.en.yml index f268bff..be62a40 100644 --- a/config/locales/en/generic.en.yml +++ b/config/locales/en/generic.en.yml @@ -2,9 +2,9 @@ en:    signup: "Sign Up"    login: "Log In"    logout: "Log Out" -   +    cancel: "Cancel" -   +    created: "Created"    created_by_on: "Created by %{user} on %{time}"    updated: "Updated" @@ -25,3 +25,5 @@ en:    create_thing: "Create %{thing}"    overview: "Overview" +  choose: "Choose" +  enter_amount: "Enter amount" diff --git a/config/locales/en/users.en.yml b/config/locales/en/users.en.yml index bc8c19d..f2e60af 100644 --- a/config/locales/en/users.en.yml +++ b/config/locales/en/users.en.yml @@ -5,6 +5,8 @@ en:        identities: "Usernames"        tickets: "Tickets"    user_control_panel: "user control panel" +  donations: "Donations" +  subscriptions: "Subscriptions"    account_settings: "Account Settings"    username: "Username"    password: "Password" diff --git a/config/locales/es.yml b/config/locales/es.yml index f745257..1742be5 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -23,6 +23,8 @@ es:    pricing: Tarifas    about: Sobre nosotros    contact: Contacto +  donations: "Donaciones" +  subscriptions: "Subscripciones"    signup: Registrarse    login: Iniciar sesión    logout: Cerrar sesión @@ -128,6 +130,11 @@ es:    description: Descripción    cost: Coste    free: Gratuito +  new_donation: "Nueva Donación" +  donation_info: "Por favor, llena los detalles de tu donación (esta no será cargada a tu cuenta, si tienes una):" +  donation_amount: "Ingrese el monto" +  donate: "Donar" +  personal_info: "Por favor, ingresa tu información personal"    support_tickets: Soporte    email_notice_text: Ha sido añadido un nuevo comentario a esta instancia de ayuda.    email_no_reply_text: No responda a este correo. @@ -201,3 +208,28 @@ es:      hints:        ticket:          email: Proporcione una dirección de correo electrónico para que se le notifique cuando se actualice esta instancia. + +#Payment (except for some donation, which is in user, mostly) +#donations +  donation_sucess: "¡Felicitaciones! Tu donación ha sido exitosa" +  donation_not_sucess: "Algo malo sucedió al procesar tu donación. Por favor, intenta de nuevo" +#subscriptions +  subscription_sucess: "¡Felicitaciones! Ahora estás suscrito" +  subscription_not_sucess: "Algo malo sucedió al procesar tu subscripción. Por favor, intenta de nuevo" +  unsubscription_sucess: "Tu subscripción ha sido cancelada" +  unsubscription_not_sucess: "Algo malo sucedió. Por favor, intenta de nuevo" +  subscriptions: "Subscripciones" +  lastestsubs: "Últimas Subscripciones:" +  date: "Fecha" +  unsubscribe_from: "Cancelar la subscrición a" +  no_subs: "Ninguna Sucripción" +  choose_subs: "Elige una Subscripción:" +  choose_button: "Elegir" +  new_subs: "Nueva Subscripción" +  #common +  first_name: "Nombre" +  last_name: "Apellido" +  company: "Compañía" +  phone: "Teléfono" +  personal_info: "Por favor, ingresa tu información personal:" +  pay_details: "Por favor, ingresa tus detalles de pago" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index fac4c32..2e0043b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -24,6 +24,7 @@ fr:    about: À propos de nous    contact: Contact    signup: S'enregistrer +  donations: Donation    login: Connexion    logout: Déconnexion    cancel: Annuler diff --git a/config/locales/it.yml b/config/locales/it.yml index 9e318fc..e6d746c 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -23,6 +23,7 @@ it:    pricing: Prezzario    about: A proposito di    contact: Contatti +  donations: Donazioni    signup: Registrati    login: Accedi    logout: Log Out @@ -32,7 +33,7 @@ it:    updated: Aggiornata    none: Nessuno    unknown: Sconosciuto -  admin: Admin +  admin: Administratore    anonymous: Anonimo    save: Salva    add: Aggiungi diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 256653e..3cf8820 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -23,6 +23,7 @@ pt:    pricing: Preço    about: Sobre Nós    contact: Contato +  donations: Doações    signup: Cadastrar    login: Entrar    logout: Sair diff --git a/engines/billing/README.md b/engines/billing/README.md index 3ef6153..d1e4d60 100644 --- a/engines/billing/README.md +++ b/engines/billing/README.md @@ -1,13 +1,14 @@  Billing Engine  ==================== -Currently, this engine support billing via Braintree. More backends to come later. +Currently, this engine support billing via Braintree. Braintree provides three +options for payments: Pay Pal, Bitcoin and Credit Cards.  Configuration  ----------------------------------  Start with a sandbox account, which you can get here: https://www.braintreepayments.com/get-started - +:q  Once you have registered for the sandbox, logging in will show you three important variables you will need to configure:  * merchantId @@ -46,4 +47,58 @@ You also will want to add a Plan to your Sandbox. Within the Braintree Sandbox,  Here are credit cared numbers to try in the Sandbox: -https://www.braintreepayments.com/docs/ruby/reference/sandbox
\ No newline at end of file +https://www.braintreepayments.com/docs/ruby/reference/sandbox + +How does it works +-------------------------------- + +The new implementation of Braintree uses its new API called 'v.zero'. It +consists of complementary client and server SDKs: + +1. The JS client SDK enables you to collect payment method (e.g. credit card, +PayPal) details on your website +2. The server SDKs manage all requests to the Braintree gateway. +They represent the Client-side Encryption solution that combines Braintree’s +traditional Server-to-Server (S2S) approach and  Transparent Redirect (TR) +solution. It can be described as following: + +1. The application server generates a client token for each customer (data blob) +using the Ruby SDK for the frontend that initializes the JavaScript SDK +using that client token. +2. The Braintree-provided JavaScript library encrypts sensitive data using the +public key and communicates with Braintree before the form is ever posted to +your server. +3. Once the data reaches Braintree’s servers, it is decrypted using the keypair’s +private key, then returns a payment method nonce to your client code. Your code +relays this nonce to your server. +4. Your server-side code provides the payment method nonce to the Ruby SDK to +perform Braintree operations (in this case either donations or subcriptions). + +What is included +-------------------------------- + +Current implementation with 'v.zero' provides: +1. Donations and subscriptions. + +2. Three payment methods: Bitcoin, Pay Pal and Credit Cards. + +3. Creation and storage of customers (stored in 'The Vault') + +4. Ability to donate as anonymous user. + +5. Subscription or unsubscriptions to plans. + +6. Recurring billing. + +7. Storing Multiple Credit Cards. + +Bitcoin +-------------------------------- + +In order for Bitcoin to work, you need to write Braintree's community and ask +them to allow that payment method. Bitcoin is implemented via Coinbase. + +Learn about this here: +https://developers.braintreepayments.com/javascript+ruby/guides/coinbase/configuration + +Contact: coinbase@braintreepayments.com diff --git a/engines/billing/app/controllers/billing_admin_controller.rb b/engines/billing/app/controllers/billing_admin_controller.rb index e11d4ee..23740d6 100644 --- a/engines/billing/app/controllers/billing_admin_controller.rb +++ b/engines/billing/app/controllers/billing_admin_controller.rb @@ -1,6 +1,9 @@  class BillingAdminController < BillingBaseController    before_filter :require_admin +  #not sure if this controller is still needed. Admin can easly acess +  #braintree's dashboard and check subscriptions. Don't know if everything +  #should be 'self contained' in web_app""    def show      br_atleast_90_days = Braintree::Subscription.search do |search| diff --git a/engines/billing/app/controllers/billing_base_controller.rb b/engines/billing/app/controllers/billing_base_controller.rb index 0453677..c343938 100644 --- a/engines/billing/app/controllers/billing_base_controller.rb +++ b/engines/billing/app/controllers/billing_base_controller.rb @@ -13,6 +13,9 @@ class BillingBaseController < ApplicationController      elsif params[:id]        @user = User.find(params[:id])      else +      #not sure if this is still needed. Donations work with either customer or +      #anonymous_user. Subscriptions work with customer. Customer belongs to +      #user.        # TODO        # hacky, what are cases where @user hasn't yet been set? certainly some cases with subscriptions and payments        @user = current_user diff --git a/engines/billing/app/controllers/credit_card_info_controller.rb b/engines/billing/app/controllers/credit_card_info_controller.rb deleted file mode 100644 index fbaa6f1..0000000 --- a/engines/billing/app/controllers/credit_card_info_controller.rb +++ /dev/null @@ -1,35 +0,0 @@ -class CreditCardInfoController < ApplicationController -  before_filter :require_login, :set_user - -  def edit -    @credit_card = Braintree::CreditCard.find(params[:id]) -    customer = Customer.find_by_user_id(@user.id) -    if customer and customer.braintree_customer_id == @credit_card.customer_id -      @tr_data = Braintree::TransparentRedirect. -        update_credit_card_data(:redirect_url => confirm_credit_card_info_url, -                                :payment_method_token => @credit_card.token) -    else -      access_denied -    end - -  end - -  def confirm -    @result = Braintree::TransparentRedirect.confirm(request.query_string) -    if @result.success? -      render :action => "confirm" -    else -      @credit_card = Braintree::CreditCard.find(@result.params[:payment_method_token]) -      render :action => "edit" -    end -  end - - -  private - -    def set_user -    # this assumes anybody, even an admin, will not access for another user. -    @user = current_user -  end - -end diff --git a/engines/billing/app/controllers/customer_controller.rb b/engines/billing/app/controllers/customer_controller.rb deleted file mode 100644 index 6cbcb44..0000000 --- a/engines/billing/app/controllers/customer_controller.rb +++ /dev/null @@ -1,64 +0,0 @@ -class CustomerController < BillingBaseController -  before_filter :require_login, :fetch_customer - -  def show -    if @customer -      @customer.with_braintree_data! -      @default_cc = @customer.default_credit_card -      @active_subscription = @customer.subscriptions -      @transactions = @customer.braintree_customer.transactions -    end -  end - -  def new -    if @customer.has_payment_info? -      redirect_to edit_customer_path(@user), :notice => 'Here is your saved customer data' -    else -      fetch_new_transparent_redirect_data -    end -  end - -  def edit -    fetch_edit_transparent_redirect_data -  end - -  def confirm -    @result = Braintree::TransparentRedirect.confirm(request.query_string) -    if @result.success? -      @customer.braintree_customer =  @result.customer -      @customer.save -      render :action => "confirm" -    elsif @customer.has_payment_info? -      fetch_edit_transparent_redirect_data -      render :action => "edit" -    else -      fetch_new_transparent_redirect_data -      render :action => "new" -    end -  end - -  protected - -  def fetch_new_transparent_redirect_data -    access_denied unless @user == current_user # admins cannot do this for others -    @tr_data = Braintree::TransparentRedirect. -      create_customer_data(:redirect_url => confirm_customer_url) -  end - -  def fetch_edit_transparent_redirect_data -    access_denied unless @user == current_user # admins cannot do this for others -    @customer.with_braintree_data! -    @default_cc = @customer.default_credit_card -    @tr_data = Braintree::TransparentRedirect. -      update_customer_data(:redirect_url => confirm_customer_url, -                           :customer_id => @customer.braintree_customer_id) ##?? -  end - -  def fetch_customer -    @customer = Customer.find_by_user_id(@user.id) -    if @user == current_user -      @customer ||= Customer.new(user: @user) -    end -    access_denied unless (@customer and (@customer.user == current_user)) or admin? -  end -end diff --git a/engines/billing/app/controllers/payments_controller.rb b/engines/billing/app/controllers/payments_controller.rb index fce6570..871f1b4 100644 --- a/engines/billing/app/controllers/payments_controller.rb +++ b/engines/billing/app/controllers/payments_controller.rb @@ -2,19 +2,14 @@ class PaymentsController < BillingBaseController    before_filter :require_login, :only => [:index]    def new -    fetch_transparent_redirect -  end - -  def confirm -    @result = Braintree::TransparentRedirect.confirm(request.query_string) -    if @result.success? -      render :action => "confirm" +    if current_user.has_payment_info? +      @client_token = Braintree::ClientToken.generate(customer_id: current_user.braintree_customer_id)      else -      fetch_transparent_redirect -      render :action => "new" -    end +      @client_token = Braintree::ClientToken.generate +   end    end +# not sure if this should be kept    def index      access_denied unless admin? or (@user == current_user)      customer = Customer.find_by_user_id(@user.id) @@ -23,12 +18,49 @@ class PaymentsController < BillingBaseController      @transactions = braintree_data.transactions    end -  protected +  def confirm +    make_transaction +    if @result.success? +      flash[:success] = I18n.t(:donation_sucess) +    else +      flash[:error] = I18n.t(:donation_not_sucess) +    end +    redirect_to action: :new, locale: params[:locale] +  end -  def fetch_transparent_redirect -    @tr_data = Braintree::TransparentRedirect.transaction_data redirect_url: confirm_payment_url, -      transaction: { type: "sale", options: {submit_for_settlement: true } } +  private +  def make_transaction +    if current_user.has_payment_info? +      transact_without_user_info +   elsif current_user.is_anonymous? +      transact_without_user_info +    else +      transact_with_user_info +    end    end +  def transact_with_user_info +    @result = Braintree::Transaction.sale( +               amount: params[:amount], +               payment_method_nonce: params[:payment_method_nonce], +               customer: { +                  first_name: params[:first_name], +                  last_name: params[:last_name], +                  company: params[:company], +                  email: current_user.email, +                  phone: params[:phone] +                }, +                options: { +                  store_in_vault: true +                }) +    current_user.update_attributes(braintree_customer_id: @result.transaction.customer_details.id) if @result.success? +  end + +  def transact_without_user_info +    @result = Braintree::Transaction.sale( +               amount: params[:amount], +               payment_method_nonce: params[:payment_method_nonce], +              ) +  end  end diff --git a/engines/billing/app/controllers/subscriptions_controller.rb b/engines/billing/app/controllers/subscriptions_controller.rb index f066b3c..1d29cac 100644 --- a/engines/billing/app/controllers/subscriptions_controller.rb +++ b/engines/billing/app/controllers/subscriptions_controller.rb @@ -1,63 +1,72 @@  class SubscriptionsController < BillingBaseController    before_filter :require_login -  before_filter :fetch_subscription, :only => [:show, :destroy] -  before_filter :confirm_cancel_subscription, :only => [:destroy] -  before_filter :confirm_self_or_admin, :only => [:index] -  before_filter :confirm_no_pending_active_pastdue_subscription, :only => [:new, :create] -  # for now, admins cannot create or destroy subscriptions for others: -  before_filter :confirm_self, :only => [:new, :create] +  before_filter :assign_user +  before_filter :confirm_cancel_subscription, only: [:destroy] +  before_filter :generate_client_token, only: [:show] +  before_filter :get_braintree_customer, only: [:subscribe] -  def new -    # don't show link to subscribe if they are already subscribed? -    credit_card = @customer.default_credit_card #safe to assume default? -    @payment_method_token = credit_card.token -    @plans = Braintree::Plan.all +  def index +    if @user.subscription_id +      @subscription = Braintree::Subscription.find @user.subscription_id +      @plan = Braintree::Plan.all.select{ |plan| plan.id == @subscription.plan_id }.first +    else +      @subscriptions = Braintree::Plan.all +    end    end -  # show has no content, so not needed at this point. - -  def create -    @result = Braintree::Subscription.create( :payment_method_token => params[:payment_method_token], :plan_id => params[:plan_id] ) -    #if you want to test pastdue, can add :price => '2001', :trial_period => true,:trial_duration => 1,:trial_duration_unit => "day" and then wait a day +  def show +    @plan = Braintree::Plan.all.select{ |plan| plan.id == params[:id] }.first    end -  def destroy -    @result = Braintree::Subscription.cancel params[:id] +  def subscribe +    @result = Braintree::Subscription.create(payment_method_token: @customer.payment_methods.first.token, +                                             plan_id: params[:id]) +    if @result.success? +      @user.update_attributes subscription_id: @result.subscription.id +      flash[:success] = I18n.t(:subscription_sucess) +    else +      flash[:error] = I18n.t(:subscription_not_sucess) +    end +    redirect_to action: :index, locale: params[:locale]    end -  def index -    customer = Customer.find_by_user_id(@user.id) -    @subscriptions = customer.subscriptions(nil, false) +  def unsubscribe +    @result = Braintree::Subscription.cancel(@user.subscription_id) +    if @result.success? +      @user.update_attributes subscription_id: nil +      flash[:success] = I18n.t(:unsubscription_sucess) +    else +      flash[:error] = I18n.t(:unsubscription_not_sucess) +    end +    redirect_to action: :index, locale: params[:locale]    end    private - -  def fetch_subscription -    @subscription = Braintree::Subscription.find params[:id] -    @credit_card = Braintree::CreditCard.find @subscription.payment_method_token -    @subscription_customer_id = @credit_card.customer_id -    current_user_customer = Customer.find_by_user_id(current_user.id) -    access_denied unless admin? or (current_user_customer and current_user_customer.braintree_customer_id == @subscription_customer_id) - -  end - -  def confirm_cancel_subscription -    access_denied unless view_context.allow_cancel_subscription(@subscription) +  def assign_user +    @user = current_user    end -  def confirm_no_pending_active_pastdue_subscription -    @customer = Customer.find_by_user_id(@user.id) -    if subscription = @customer.subscriptions # will return pending, active or pastdue subscription, if it exists -      redirect_to user_subscription_path(@user, subscription.id), :notice => 'You already have a subscription' +  def generate_client_token +    if current_user.braintree_customer_id +      @client_token = Braintree::ClientToken.generate(customer_id: current_user.braintree_customer_id) +    else +     @client_token = Braintree::ClientToken.generate      end    end -  def confirm_self -    @user == current_user -  end - -  def confirm_self_or_admin -    access_denied unless confirm_self or admin? +  def get_braintree_customer +    if current_user.braintree_customer_id +      @customer = Braintree::Customer.find(current_user.braintree_customer_id) +    else +      @customer = Braintree::Customer.create( +                              payment_method_nonce: params[:payment_method_nonce], +                              first_name: params[:first_name], +                              last_name: params[:last_name], +                              company: params[:company], +                              email: current_user.email, +                              phone: params[:phone] +                            ).customer +      current_user.update_attributes braintree_customer_id: @customer.id +    end    end -  end diff --git a/engines/billing/app/helpers/billing_helper.rb b/engines/billing/app/helpers/billing_helper.rb index b9e5e2e..6d1df5a 100644 --- a/engines/billing/app/helpers/billing_helper.rb +++ b/engines/billing/app/helpers/billing_helper.rb @@ -1,5 +1,6 @@  module BillingHelper +  #deprecated.. erase?    def braintree_form_for(object, options = {}, &block)      options.reverse_merge! params: @result && @result.params[object],        errors: @result && @result.errors.for(object), @@ -14,10 +15,11 @@ module BillingHelper      if (admin? and user == current_user)        billing_admin_path      else -      show_or_new_customer_link(user) +      subscriptions_path      end    end +  #deprecated.. erase?    def show_or_new_customer_link(user)      # Link to show if user is admin viewing another user, or user is already a customer.      # Otherwise link to create a new customer. @@ -28,6 +30,7 @@ module BillingHelper      end    end +  #deprecated.. erase?    # a bit strange to put here, but we don't have a subscription model    def user_for_subscription(subscription) @@ -44,6 +47,7 @@ module BillingHelper    end +  #customer needs to see active or pending?    def allow_cancel_subscription(subscription)      ['Active', 'Pending'].include? subscription.status or (admin? and subscription.status == 'Past Due')    end diff --git a/engines/billing/app/helpers/braintree_form_helper.rb b/engines/billing/app/helpers/braintree_form_helper.rb index cb322fa..31ea373 100644 --- a/engines/billing/app/helpers/braintree_form_helper.rb +++ b/engines/billing/app/helpers/braintree_form_helper.rb @@ -3,6 +3,7 @@ module BraintreeFormHelper      include ActionView::Helpers::AssetTagHelper      include ActionView::Helpers::TagHelper +    #check if needed      def initialize(object_name, object, template, options, proc)        super        @braintree_params = @options[:params] diff --git a/engines/billing/app/helpers/braintree_helper.rb b/engines/billing/app/helpers/braintree_helper.rb index 2d18b6c..a6322c5 100644 --- a/engines/billing/app/helpers/braintree_helper.rb +++ b/engines/billing/app/helpers/braintree_helper.rb @@ -1,5 +1,5 @@  module BraintreeHelper - +  #check if needed  end diff --git a/engines/billing/app/models/customer.rb b/engines/billing/app/models/customer.rb deleted file mode 100644 index 1acc7a5..0000000 --- a/engines/billing/app/models/customer.rb +++ /dev/null @@ -1,58 +0,0 @@ -class Customer < CouchRest::Model::Base - -  FIELDS = [:first_name, :last_name, :phone, :website, :company, :fax, :addresses, :credit_cards, :custom_fields] -  attr_accessor *FIELDS - -  use_database "customers" -  belongs_to :user -  belongs_to :braintree_customer - -  # Braintree::Customer - stored on braintrees servers - we only have the id. -  def braintree_customer -    @braintree_customer ||= Braintree::Customer.find(braintree_customer_id) -  end - -  validates :user, presence: true - -  design do -    view :by_user_id -    view :by_braintree_customer_id -  end - -  def has_payment_info? -    !!braintree_customer_id -  end - -  # from braintree_ruby_examples/rails3_tr_devise and should be tweaked -  def with_braintree_data! -    return self unless has_payment_info? - -    FIELDS.each do |field| -      send(:"#{field}=", braintree_customer.send(field)) -    end -    self -  end - -  def default_credit_card -    return unless has_payment_info? - -    credit_cards.find { |cc| cc.default? } -  end - -  # based on 2nd parameter, either returns the single active subscription (or nil if there isn't one), or an array of all subsciptions -  def subscriptions(braintree_data=nil, only_pending_active_pastdue=true) -    self.with_braintree_data! -    return unless has_payment_info? - -    subscriptions = [] -    self.default_credit_card.subscriptions.each do |sub| -      if only_pending_active_pastdue and ['Pending', 'Active','Past Due'].include? sub.status -        return sub -      else -        subscriptions << sub -      end -    end -    only_pending_active_pastdue ? nil : subscriptions -  end - -end diff --git a/engines/billing/app/views/billing_admin/show.html.haml b/engines/billing/app/views/billing_admin/show.html.haml index 0382cf0..11f3928 100644 --- a/engines/billing/app/views/billing_admin/show.html.haml +++ b/engines/billing/app/views/billing_admin/show.html.haml @@ -1,7 +1,9 @@ +/ same concern as in controller  %legend= t(:more_than_90_days_past_due)  = render(:partial => "subscriptions/subscription_details", :collection => @past_due_atleast_90_days, :as => 'subscription', :locals => {:show_user => true}) || t(:none)  %legend= t(:all_past_due)  = render(:partial => "subscriptions/subscription_details", :collection => @all_past_due, :as => 'subscription', :locals => {:show_user => true}) || t(:none) -%legend= t(:your_settings) -= link_to 'view own billing settings', show_or_new_customer_link(current_user)
\ No newline at end of file +/needed? +/%legend= t(:your_settings) +/= link_to 'view own billing settings', show_or_new_customer_link(current_user) diff --git a/engines/billing/app/views/credit_card_info/confirm.html.haml b/engines/billing/app/views/credit_card_info/confirm.html.haml deleted file mode 100644 index 9dd8176..0000000 --- a/engines/billing/app/views/credit_card_info/confirm.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%h1 Payment Info Confirmation -%p Your payment information was successfully saved. -%dl -  %dt Credit Card -  %dd= @result.credit_card.masked_number diff --git a/engines/billing/app/views/credit_card_info/edit.html.haml b/engines/billing/app/views/credit_card_info/edit.html.haml deleted file mode 100644 index 9e44344..0000000 --- a/engines/billing/app/views/credit_card_info/edit.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -%h1 Change Credit Card -- if @result -  #total-errors{:style => "color:red;"} -    = h(@result.errors.size) -    error(s) -= braintree_form_for :credit_card, :existing => @credit_card do |f| -  = field_set_tag "Credit Card" do -    %dl -      %dt= f.label :number, 'Number' -      %dd= f.text_field :number -      %dt= f.label :expiration_date, 'Expiration Date (MM/YY)' -      %dd= f.text_field :expiration_date -      %dt= f.label :cvv, 'CVV' -      %dd= f.text_field :cvv -  = hidden_field_tag :tr_data, @tr_data -  = f.button :wrapped, 'Save Payment Info', cancel: edit_customer_path(@user.id) diff --git a/engines/billing/app/views/customer/_customer_data.html.haml b/engines/billing/app/views/customer/_customer_data.html.haml deleted file mode 100644 index 439ae5c..0000000 --- a/engines/billing/app/views/customer/_customer_data.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -%legend= t(:customer_information) -%dl -  %dt First Name -  %dd= @customer.first_name -  %dt Last Name -  %dd= @customer.last_name -  %dt Phone -  %dd= @customer.phone -%legend= t(:credit_card_information) -%dl -  %dt Number -  %dd= @default_cc.masked_number -  %dt Expiration Date -  %dd= @default_cc.expiration_date -  - if current_user == @user -    = btn t(:edit_saved_data), edit_customer_path(@user.id) diff --git a/engines/billing/app/views/customer/_transaction.html.haml b/engines/billing/app/views/customer/_transaction.html.haml deleted file mode 100644 index e69de29..0000000 --- a/engines/billing/app/views/customer/_transaction.html.haml +++ /dev/null diff --git a/engines/billing/app/views/customer/confirm.html.haml b/engines/billing/app/views/customer/confirm.html.haml deleted file mode 100644 index eab9616..0000000 --- a/engines/billing/app/views/customer/confirm.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -%h1 Payment Info Confirmation -%p Your payment information was successfully saved. -%dl -  %dt First Name -  %dd= @result.customer.first_name -  %dt Last Name -  %dd= @result.customer.last_name -  %dt Phone -  %dd= @result.customer.phone -  %dt Credit Card -  - @result.customer.credit_cards.each do |cc| -    %dd= cc.masked_number -- customer = Customer.find_by_user_id(@user.id) -= btn 'View Customer Info', show_customer_path(@user.id) diff --git a/engines/billing/app/views/customer/edit.html.haml b/engines/billing/app/views/customer/edit.html.haml deleted file mode 100644 index f461fcc..0000000 --- a/engines/billing/app/views/customer/edit.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -- if @result -  #total-errors{:style => "color:red;"} -    = h(@result.errors.size) -    error(s) -= braintree_form_for :customer, existing: @customer do |f| -  = field_set_tag "Customer" do -    %dl -      %dt= f.label :first_name, 'First Name' -      %dd= f.text_field :first_name -      %dt= f.label :last_name, 'Last Name' -      %dd= f.text_field :last_name -      %dt= f.label :phone, 'Phone' -      %dd= f.text_field :phone -      - if @default_cc -        = # todo, as they will need a credit card, so not sure about conditional? -        %dt= t(:stored_credit_card) -        %dd -        = @default_cc.masked_number -        = btn t(:change_credit_card), edit_credit_card_info_path(:id => @default_cc.token) -  = hidden_field_tag :tr_data, @tr_data -  .form-actions -    = f.submit t(:save_customer_info), :class => 'btn btn-primary' -    = btn t(:cancel), show_customer_path(@user) diff --git a/engines/billing/app/views/customer/new.html.haml b/engines/billing/app/views/customer/new.html.haml deleted file mode 100644 index e1f5ba9..0000000 --- a/engines/billing/app/views/customer/new.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -- if @result -  #total-errors{:style => "color:red;"} -    = h(@result.errors.size) -    error(s) -= braintree_form_for :customer do |f| -  = field_set_tag "Customer" do -    %dl -      %dt= f.label :first_name, 'First Name' -      %dd= f.text_field :first_name -      %dt= f.label :last_name, 'Last Name' -      %dd= f.text_field :last_name -      %dt= f.label :phone, 'Phone' -      %dd= f.text_field :phone -  = field_set_tag "Credit Card" do -    - f.fields_for :credit_card do |cc| -      %dl -        %dt= cc.label :number, 'Number' -        %dd= cc.text_field :number -        %dt= cc.label :expiration_date, 'Expiration Date (MM/YY)' -        %dd= cc.text_field :expiration_date -        %dt= cc.label :cvv, 'CVV' -        %dd= cc.text_field :cvv -  = hidden_field_tag :tr_data, @tr_data -  = f.submit 'Save Payment Info' diff --git a/engines/billing/app/views/customer/show.html.haml b/engines/billing/app/views/customer/show.html.haml deleted file mode 100644 index ce4d01a..0000000 --- a/engines/billing/app/views/customer/show.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -- if admin? and !@customer -  = t(:no_saved_customer) -- else -  = render :partial => 'customer_data' -  %legend= t(:last_three_transactions) -  - counter = 0 -  = # these will be ordered with most recently created first, per http://stackoverflow.com/questions/16425475/ -  - @transactions.each do |t| -    - break if counter > 2 # not ruby-like, but object is a Braintree::ResourceCollection so limited methods available -    = render :partial => "payments/transaction_details", :locals => {:transaction => t} -    - counter += 1 -  = btn :transaction_history, user_payments_path(@user) -  %legend= t(:subscriptions) -  - if @active_subscription -    = render :partial => "subscriptions/subscription_details", :locals => {:subscription => @active_subscription} -  - else -    %p -      = t(:no_relevant_subscription) -    - if current_user == @user -      %p -        .form-actions -          = btn :subscribe_to_plan, new_subscription_path -  %p -    = link_to t(:all_subscriptions), user_subscriptions_path(@user) - -.form-actions -  = btn :make_donation, new_payment_path, :type => 'primary' diff --git a/engines/billing/app/views/payments/_customer_form.html.haml b/engines/billing/app/views/payments/_customer_form.html.haml new file mode 100644 index 0000000..70b9b97 --- /dev/null +++ b/engines/billing/app/views/payments/_customer_form.html.haml @@ -0,0 +1,10 @@ +%p +  = t(:personal_info) +%div +  = text_field_tag :first_name, "",placeholder: "#{t(:first_name)}", class: "radius" +%div +  = text_field_tag :last_name, "",placeholder: "#{t(:last_name)}", class: "radius" +%div +  = text_field_tag :company, "",placeholder: "#{t(:company)}", class: "radius" +%div +  = text_field_tag :phone, "",placeholder: "#{t(:phone)}", class: "radius" diff --git a/engines/billing/app/views/payments/_non_customer_fields.html.haml b/engines/billing/app/views/payments/_non_customer_fields.html.haml deleted file mode 100644 index 77cfe95..0000000 --- a/engines/billing/app/views/payments/_non_customer_fields.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -= field_set_tag "Personal Information" do -  = f.fields_for :customer do |c| -    %div= c.label :first_name, "First Name" -    %div= c.text_field :first_name -    %div= c.label :last_name, "Last Name" -    %div= c.text_field :last_name -    %div= c.label :email, "Email" -    %div= c.text_field :email -= field_set_tag "Credit Card" do -  = f.fields_for :credit_card do |c| -    %div= c.label :number, "Number" -    %div= c.text_field :number -    %div= c.label :expiration_date, "Expiration Date (MM/YY)" -    %div= c.text_field :expiration_date -    %div= c.label :cvv, "CVV" -    %div= c.text_field :cvv
\ No newline at end of file diff --git a/engines/billing/app/views/payments/_transaction_details.html.haml b/engines/billing/app/views/payments/_transaction_details.html.haml deleted file mode 100644 index 85e4f6a..0000000 --- a/engines/billing/app/views/payments/_transaction_details.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -%p -  = transaction.id -  Type: -  = transaction.type -  Amount: -  = number_to_currency(transaction.amount) -  Status: -  = transaction.status -  Date -  = transaction.created_at.strftime("%Y-%m-%d") -  - if sub_start = transaction.subscription_details.billing_period_start_date -    From subscription which started -    = sub_start -  - else # should not have any of these -    Not paid as part of subscription
\ No newline at end of file diff --git a/engines/billing/app/views/payments/confirm.html.haml b/engines/billing/app/views/payments/confirm.html.haml deleted file mode 100644 index 45af3c9..0000000 --- a/engines/billing/app/views/payments/confirm.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -%h1 Payment Result -%div Thank you for your donation. -%h2 Transaction Details -%table -  %tr -    %td Amount -    %td -      $#{@result.transaction.amount} -  %tr -    %td Transaction ID: -    %td= @result.transaction.id -  %tr -    %td First Name: -    %td= h @result.transaction.customer_details.first_name -  %tr -    %td Last Name: -    %td= h @result.transaction.customer_details.last_name -  %tr -    %td Email: -    %td= h @result.transaction.customer_details.email -  %tr -    %td Credit Card: -    %td= h @result.transaction.credit_card_details.masked_number -  %tr -    %td Card Type: -    %td= h @result.transaction.credit_card_details.card_type
\ No newline at end of file diff --git a/engines/billing/app/views/payments/index.html.haml b/engines/billing/app/views/payments/index.html.haml index 7a89917..01aa660 100644 --- a/engines/billing/app/views/payments/index.html.haml +++ b/engines/billing/app/views/payments/index.html.haml @@ -1,5 +1,6 @@ +/ check if necessary  %h2=t :transaction_history  - if (@transactions.count == 0)    = t(:no_transaction_history)  - @transactions.each do |t| -  = render :partial => "transaction_details", :locals => {:transaction => t}
\ No newline at end of file +  = render :partial => "transaction_details", :locals => {:transaction => t} diff --git a/engines/billing/app/views/payments/new.html.haml b/engines/billing/app/views/payments/new.html.haml index e9a8273..67018b2 100644 --- a/engines/billing/app/views/payments/new.html.haml +++ b/engines/billing/app/views/payments/new.html.haml @@ -1,17 +1,20 @@ -%h1 -  = t(:Donation) -- if logged_in? -  = t(:donation_not_payment) -- if @result and @result.errors.size > 0 -  %div{:style => "color: red;"} -    = h @result.errors.size -    error(s) -- if @result and @result.transaction and @result.transaction.status != 'success' -  %div{:style => "color: red;"} -    = t(:processor_declined) -= braintree_form_for :transaction, :html => {:autocomplete => "off"} do |f| -  = f.label :amount, t(:amount) -  = f.text_field :amount -  = render :partial => 'non_customer_fields', :locals => {:f => f} -  = hidden_field_tag :tr_data, @tr_data -  = f.submit "Submit Donation", :class => 'btn btn-primary' +%h2.mbs +  = t(:new_donation) +%br/ += form_tag confirm_payment_path, id: "checkout-form" do +  - if current_user and !current_user.has_payment_info? +    = render 'customer_form' unless current_user.is_anonymous? +  %p +    = t(:donation_info) +  %div{:id => "payment-form" } +  %div{:id => "coinbase-container-id" } +  %input{:name => "amount", :placeholder => "#{t(:donation_amount)}", :type => "text"} +    %input.btn.btn-primary{:type => "submit", :value => "#{t(:donate)}"} +%script{:src => "https://js.braintreegateway.com/v2/braintree.js"} +:javascript +  var clientToken = "#{@client_token}"; +  braintree.setup(clientToken, "dropin", { +    container: "payment-form", +    form: "checkout-form", +    coinbase: { container: "coinbase-container-id" } +  }); diff --git a/engines/billing/app/views/subscriptions/_customer_form.html.haml b/engines/billing/app/views/subscriptions/_customer_form.html.haml new file mode 100644 index 0000000..70b9b97 --- /dev/null +++ b/engines/billing/app/views/subscriptions/_customer_form.html.haml @@ -0,0 +1,10 @@ +%p +  = t(:personal_info) +%div +  = text_field_tag :first_name, "",placeholder: "#{t(:first_name)}", class: "radius" +%div +  = text_field_tag :last_name, "",placeholder: "#{t(:last_name)}", class: "radius" +%div +  = text_field_tag :company, "",placeholder: "#{t(:company)}", class: "radius" +%div +  = text_field_tag :phone, "",placeholder: "#{t(:phone)}", class: "radius" diff --git a/engines/billing/app/views/subscriptions/_subscription_details.html.haml b/engines/billing/app/views/subscriptions/_subscription_details.html.haml index 6145c95..e6cf87d 100644 --- a/engines/billing/app/views/subscriptions/_subscription_details.html.haml +++ b/engines/billing/app/views/subscriptions/_subscription_details.html.haml @@ -1,3 +1,4 @@ +/needed?  %p    - if local_assigns[:show_user]      User: @@ -23,4 +24,4 @@    Status:    %font{:color => color}      = subscription.status -  - # would be good to get plan name but not sure if that is possible?
\ No newline at end of file +  - # would be good to get plan name but not sure if that is possible? diff --git a/engines/billing/app/views/subscriptions/create.html.haml b/engines/billing/app/views/subscriptions/create.html.haml deleted file mode 100644 index 2b6c5e9..0000000 --- a/engines/billing/app/views/subscriptions/create.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- if @result.success? -  %h1 -    Subscription Status  -    = @result.subscription.status -  = render :partial => "subscription_details", :locals => {:subscription => @result.subscription} -- else -  %h1 -    Error: -    = @result.message 
\ No newline at end of file diff --git a/engines/billing/app/views/subscriptions/destroy.html.haml b/engines/billing/app/views/subscriptions/destroy.html.haml deleted file mode 100644 index e6e8578..0000000 --- a/engines/billing/app/views/subscriptions/destroy.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -- if @result.success? -  Subscription destroyed -- else -  Error: -  = @result.message -%p -  = btn 'Customer Information', show_customer_path(@user) diff --git a/engines/billing/app/views/subscriptions/index.html.haml b/engines/billing/app/views/subscriptions/index.html.haml index 1e3fb25..70fbf8d 100644 --- a/engines/billing/app/views/subscriptions/index.html.haml +++ b/engines/billing/app/views/subscriptions/index.html.haml @@ -1,8 +1,29 @@ -%h2=t :all_subscriptions -- pending_active_pastdue = false -- @subscriptions.each do |s| -  - if ['Pending', 'Active','Past Due'].include? s.status -    - pending_active_pastdue = true -  = render :partial => "subscription_details", :locals => {:subscription => s} -- if !pending_active_pastdue and @user == current_user -  = btn 'subscribe to plan', new_subscription_path +%h2.mbs +  = t(:subscriptions) +%h4{ :style => "line-height: 300%;" } +  .last +    = t(:lastestsubs) +  - if @user.subscription_id +    %ul +      - @subscription.transactions.each do |transaction| +        %li +          %p{ :style => "font-size: 14px; font-weight: normal;" } +            #{t(:date)} #{transaction.created_at} +    %ul +      = link_to "#{t(:unsubscribe_from)} #{@plan.name}", unsubscribe_subscription_path(@subscription.plan_id), method: :delete, class: "btn btn-danger" +      %br +  - else +    %p{ :style => "font-size: 14px; font-weight: normal;"} +      =t(:no_subs) +    %h4 +      =t(:choose_subs) +    %br +    %ul.nav.nav-tabs.nav-stacked +      - @subscriptions.each do |subscription| +        .well +          = subscription.name +          = "$" + subscription.price.to_s +          %div{ :style => "line-height: 300%;" } +            = link_to t(:choose_button), subscription_path(subscription.id), class: "btn btn-info" + + diff --git a/engines/billing/app/views/subscriptions/new.html.haml b/engines/billing/app/views/subscriptions/new.html.haml deleted file mode 100644 index 4183458..0000000 --- a/engines/billing/app/views/subscriptions/new.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -- if @payment_method_token -  %h1 -    Subscribe to plan -  = #currently just one plan  -  = @plans[0].name -  = number_to_currency(@plans[0].price) -  = simple_form_for :subscription, :url => :subscriptions do |f| -    = hidden_field_tag :payment_method_token, @payment_method_token -    = hidden_field_tag :plan_id, @plans[0].id -    .form-actions -      = f.submit t(:subscribe), :class => 'btn btn-primary' -- else -  = t(:must_create_customer) -  %p -    = link_to t(:create_new_customer), new_customer_path diff --git a/engines/billing/app/views/subscriptions/show.html.haml b/engines/billing/app/views/subscriptions/show.html.haml index 246ebf0..45c54cc 100644 --- a/engines/billing/app/views/subscriptions/show.html.haml +++ b/engines/billing/app/views/subscriptions/show.html.haml @@ -1,7 +1,19 @@ -%h1 -  - if @subscription.status == 'Active' -    Current -  Subscription -= render :partial => "subscription_details",  :locals => {:subscription => @subscription} -- if allow_cancel_subscription(@subscription) -  = destroy_btn :cancel_subscription, user_subscription_path(@user, @subscription.id),  type: 'danger' +%script{:src => "https://js.braintreegateway.com/v2/braintree.js"} +%h2.mbs +  = t(:new_subs) += simple_form_for :subscription, :url => subscribe_subscription_path(@plan.id), :id => "checkout-form" do |f| +  - if current_user and !current_user.has_payment_info? +    = render 'customer_form' +  %br/ +  %p +    =t(:pay_details) +  #payment-form +  #coinbase-container-id +  .form-actions +    = f.submit t(:subscribe), :class => 'btn btn-primary' +  :javascript +    var clientToken = "#{@client_token}"; +    braintree.setup(clientToken, "dropin", { +    container: "payment-form", +    coinbase: { container: "coinbase-container-id" } +    }); diff --git a/engines/billing/config/locales/en.yml b/engines/billing/config/locales/en.yml index 1300958..0bc167f 100644 --- a/engines/billing/config/locales/en.yml +++ b/engines/billing/config/locales/en.yml @@ -8,4 +8,32 @@ en:    plan: "Plan"    description: "Description"    cost: "Cost" -  free: "Free"
\ No newline at end of file +  free: "Free" +  #payments (all have spanish trans) +  #donations +  new_donation: "New Donation" +  donation_info: "Please enter your donation details (this is a donation and will not be applied towards your account, if you have one):" +  donation_amount: "Enter amount" +  donate: "Donate" +  donation_sucess: "Congratulations! Your transaction has been successfull!" +  donation_not_sucess: "Something went wrong while processing your donation. Please try again!" +  #subscriptions +  subscription_sucess: "Congratulations! Your are now subscribed" +  subscription_not_sucess: "Something went wrong while processing your subscription. Please try again" +  unsubscription_sucess: "You have been unsubscribed!" +  unsubscription_not_sucess: "Something went wrong. Please try again!" +  subscriptions: "Subscriptions" +  lastestsubs: "Lastest Subscriptions:" +  date: "Date" +  unsubscribe_from: "Unsuscribe from" +  no_subs: "No Subscriptions" +  choose_subs: "Choose Subscription:" +  choose_button: "Choose" +  new_subs: "New Subscription" +  #common +  first_name: "First Name" +  last_name: "Last Name" +  company: "Company" +  phone: "Phone" +  personal_info: "Please enter your personal info:" +  pay_details: "Please enter payment details" diff --git a/engines/billing/config/routes.rb b/engines/billing/config/routes.rb index 6bbe501..357c55b 100644 --- a/engines/billing/config/routes.rb +++ b/engines/billing/config/routes.rb @@ -1,25 +1,27 @@  Rails.application.routes.draw do    scope "(:locale)", :locale => CommonLanguages.match_available do -    match 'payments/new' => 'payments#new', :as => :new_payment -    match 'payments/confirm' => 'payments#confirm', :as => :confirm_payment -    resources :users do -      resources :payments, :only => [:index] -      resources :subscriptions, :only => [:index, :show, :destroy] -    end -    resources :customer, :only => [:new, :edit] -    resources :credit_card_info, :only => [:edit] +  get 'payments/new' => 'payments#new', :as => :new_payment +  post 'payments/confirm' => 'payments#confirm', :as => :confirm_payment +  #  match 'payments/new' => 'payments#new', :as => :new_payment +  #  match 'payments/confirm' => 'payments#confirm', :as => :confirm_payment +  #resources :users do +  # resources :payments, :only => [:new, :confirm] +  #  resources :subscriptions, :only => [:index, :destroy] +  #end +  resources :subscriptions, :only => [:index, :show] do +    member do +      post 'subscribe' +      delete 'unsubscribe' +    end +  end -    match 'customer/confirm/' => 'customer#confirm', :as => :confirm_customer -    match 'customer/show/:id' => 'customer#show', :as => :show_customer -    match 'credit_card_info/confirm' => 'credit_card_info#confirm', :as => :confirm_credit_card_info +  resources :customer, :only => [:new, :edit] -    resources :subscriptions, :only => [:new, :create, :update] # index, show & destroy are within users path -    match 'billing_admin' => 'billing_admin#show', :as => :billing_admin +  match 'customer/confirm/' => 'customer#confirm', :as => :confirm_customer +  match 'customer/show/:id' => 'customer#show', :as => :show_customer -    #match 'transactions/:product_id/new' => 'transactions#new', :as => :new_transaction -    #match 'transactions/confirm/:product_id' => 'transactions#confirm', :as => :confirm_transaction +  match 'billing_admin' => 'billing_admin#show', :as => :billing_admin    end -  end diff --git a/engines/billing/questions.md b/engines/billing/questions.md new file mode 100644 index 0000000..41da41f --- /dev/null +++ b/engines/billing/questions.md @@ -0,0 +1,22 @@ +Questions +==================== + +1. Should admin be able to see subscriptions? + +2. Should user be able to upgrade or downgrade subscriptions? + +3. Should confirmations emails be allowed? + +See here: +https://articles.braintreepayments.com/guides/recurring-billing/email-notifications +https://articles.braintreepayments.com/control-panel/transactions/email-receipts + +4. Braintree uses JS for dropin. I'm not sure but this may not work in browsers +such as Tor. Is there a problem? +JS can be improved and changed to 'Hosted Fields'. See here: +https://developers.braintreepayments.com/javascript+ruby/guides/hosted-fields/overview + +5. There is another payment method that can be allowed: SEPA Direct Debit (on +   beta). Should it be allowed? + + diff --git a/engines/billing/test/factories.rb b/engines/billing/test/factories.rb index 87543b2..6352211 100644 --- a/engines/billing/test/factories.rb +++ b/engines/billing/test/factories.rb @@ -14,7 +14,8 @@ FactoryGirl.define do      first_name 'Big'      last_name 'Spender'      credit_card number: TEST_CC_NUMBER, expiration_date: '04/2016' -    initialize_with { Braintree::Customer.create(attributes).customer } +    initialize_with { Braintree::Configuration.environment = :sandbox +                      Braintree::Customer.create(attributes).customer }      skip_create      factory :broken_customer do diff --git a/engines/billing/test/functional/customer_controller_test.rb b/engines/billing/test/functional/customer_controller_test.rb deleted file mode 100644 index d943e23..0000000 --- a/engines/billing/test/functional/customer_controller_test.rb +++ /dev/null @@ -1,124 +0,0 @@ -require 'test_helper' -require 'fake_braintree' - -class CustomerControllerTest < ActionController::TestCase -  include CustomerTestHelper - -  test "new assigns redirect url" do -    login -    get :new - -    assert_response :success -    assert assigns(:tr_data) -    tr_data = Braintree::Util.parse_query_string(assigns(:tr_data)) -    assert_equal confirm_customer_url, tr_data[:redirect_url] -  end - -  test "new requires login" do -    get :new - -    assert_response :redirect -    assert_redirected_to login_path -  end - -  test "edit uses params[:id]" do -    customer = stub_customer -    login customer.user -    get :edit, id: customer.user.id - -    assert_response :success -    assert assigns(:tr_data) -    tr_data = Braintree::Util.parse_query_string(assigns(:tr_data)) -    assert_equal customer.braintree_customer_id, tr_data[:customer_id] -    assert_equal confirm_customer_url, tr_data[:redirect_url] -  end - -  test "confirm customer creation" do -    login -    Braintree::TransparentRedirect.expects(:confirm).returns(success_response) -    # to_confirm = prepare_confirmation :create_customer_data, -    #   customer: FactoryGirl.attributes_for(:braintree_customer), -    #   redirect_url: confirm_customer_url - -    assert_difference("Customer.count") do -      post :confirm, braintree: :query -    end - -    assert_response :success -    assert result = assigns(:result) -    assert result.success? -    assert result.customer.id -  end - -  test "customer update" do -    customer = stub_customer -    customer.expects(:save) -    login customer.user -    Braintree::TransparentRedirect.expects(:confirm). -      returns(success_response(customer)) - -    assert_no_difference("Customer.count") do -      post :confirm, query: :from_braintree -    end - -    assert_response :success -    assert result = assigns(:result) -    assert result.success? -    assert_equal customer.braintree_customer, result.customer -  end - -  test "failed customer creation" do -    skip "can't get customer creation to fail" -    login -    FakeBraintree.decline_all_cards! -    # what is prepare_confirmation ?? this method isn't found -    to_confirm = prepare_confirmation :create_customer_data, -      customer: FactoryGirl.attributes_for(:broken_customer), -      redirect_url: confirm_customer_url -    post :confirm, to_confirm - -    FakeBraintree.clear! -    assert_response :success -    assert result = assigns(:result) -    assert !result.success? -  end - -  test "failed customer creation with stubbing" do -    login -    Braintree::TransparentRedirect.expects(:confirm).returns(failure_response) -    post :confirm, bla: :blub - -    assert_response :success -    assert_template :new -  end - -  test "failed customer update with stubbing" do -    customer = stub_customer -    login customer.user -    Braintree::TransparentRedirect.expects(:confirm).returns(failure_response) -    post :confirm, bla: :blub - -    assert_response :success -    assert_template :edit -  end - -  def failure_response -    stub success?: false, -      errors: stub(for: nil, size: 0), -      params: {} -  end - -  def success_response(customer = nil) -    stub success?: true, -      customer: braintree_customer(customer) -  end - -  def braintree_customer(customer) -    if customer -      customer.braintree_customer -    else -      FactoryGirl.build :braintree_customer -    end -  end - -end diff --git a/engines/billing/test/functional/customers_controller_test.rb b/engines/billing/test/functional/customers_controller_test.rb deleted file mode 100644 index 4d84fb0..0000000 --- a/engines/billing/test/functional/customers_controller_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'test_helper' -require 'fake_braintree' - -class CustomersControllerTest < ActionController::TestCase -  tests CustomerController - -  setup do -    InviteCodeValidator.any_instance.stubs(:validate) -    @user = FactoryGirl.create :user -    @other_user = FactoryGirl.create :user -    #FakeBraintree.clear! -    #FakeBraintree.verify_all_cards! -    testid = 'testid' -    #this wasn't actually being used -    #FakeBraintree::Customer.new({:credit_cards => [{:number=>"5105105105105100", :expiration_date=>"05/2013"}]}, {:id => testid, :merchant_id => Braintree::Configuration.merchant_id}) -    # any reason to call the create instance method on the FakeBraintree::Customer ? -    @customer = Customer.new(:user_id => @other_user.id) -    @customer.braintree_customer_id = testid -    @customer.save - -  end - -  teardown do -    @user.destroy -    @other_user.destroy -    @customer.destroy -  end - -  test "no access if not logged in" do -    get :new -    assert_login_required -    get :show, :id => @customer.braintree_customer_id -    assert_login_required -    get :edit, :id => @customer.braintree_customer_id -    assert_login_required -  end - - -  test "should get new if logged in and not customer" do -    login @user -    get :new -    assert_not_nil assigns(:tr_data) -    assert_response :success -  end - -  test "new should direct edit if user is already a customer" do -    login @other_user -    get :new -    assert_response :redirect -    assert_equal edit_customer_url(@customer.user), response.header['Location'] -  end - - -  test "show" do -    skip "show customer" -    login @other_user -    # Below will fail, as when we go to fetch the customer data, Braintree::Customer.find(params[:id]) won't find the customer as it is a FakeBraintree customer. -    #get :show, :id => @customer.braintree_customer_id - -  end - -end diff --git a/engines/billing/test/functional/payments_controller_test.rb b/engines/billing/test/functional/payments_controller_test.rb index 90b7582..e015a07 100644 --- a/engines/billing/test/functional/payments_controller_test.rb +++ b/engines/billing/test/functional/payments_controller_test.rb @@ -4,47 +4,56 @@ require 'fake_braintree'  class PaymentsControllerTest < ActionController::TestCase    include CustomerTestHelper -  test "payment when unauthorized" do -    get :new -    assert_not_nil assigns(:tr_data) -    assert_response :success +  def setup +    FakeBraintree.activate!    end -  test "successful confirmation renders confirm" do -    Braintree::TransparentRedirect.expects(:confirm).returns(success_response) -    get :confirm - -    assert_response :success -    assert_template :confirm +  def teardown +    FakeBraintree.clear!    end -  test "failed confirmation renders new" do -    Braintree::TransparentRedirect.expects(:confirm).returns(failure_response) -    get :confirm +  test "payment new" do +    get :new +    assert_not_nil assigns(:client_token)      assert_response :success -    assert_not_nil assigns(:tr_data) -    assert_template :new    end -  def failure_response -    stub success?: false, -      errors: stub(for: nil, size: 0), -      params: {}, -      transaction: stub(status: nil) +  test "sucess confirmation" do +    #already included with FakeBraintree +    #Braintree::Transaction.sale.expects(:confirm).returns(success_response) +    post :confirm, { +      amount: "100", +      payment_method_nonce: "fake-valid-nonce", +      customer: { +         first_name: "Test", +         last_name: "Testing", +         company: "RGSoC", +         email: "any@email.com", +         phone: "555-888-1234" } +    } + +    assert assigns(:result).success? +    assert_not_nil flash[:success]    end -  def success_response -    stub success?: true, -      transaction: stub_transaction +  test "failed confirmation renders new" do +    FakeBraintree.decline_all_cards! +    post :confirm, { +      amount: "100", +      payment_method_nonce: "fake-valid-nonce", +      customer: { +         first_name: "Test", +         last_name: "Testing", +         company: "RGSoC", +         email: "any@email.com", +         phone: "555-888-1234" } +    } + +    assert !assigns(:result).success? +    assert_not_nil flash[:error] +    FakeBraintree.clear!    end    # that's what you get when not following the law of demeter... -  def stub_transaction -    stub amount: "100.00", -      id: "ASDF", -      customer_details: FactoryGirl.build(:braintree_customer), -      credit_card_details: FactoryGirl.build(:braintree_customer).credit_cards.first -  end -  end diff --git a/engines/billing/test/functional/subscriptions_controller_test.rb b/engines/billing/test/functional/subscriptions_controller_test.rb index a6a1057..1e98eff 100644 --- a/engines/billing/test/functional/subscriptions_controller_test.rb +++ b/engines/billing/test/functional/subscriptions_controller_test.rb @@ -4,13 +4,78 @@ require 'fake_braintree'  class SubscriptionsControllerTest < ActionController::TestCase    include CustomerTestHelper -  test "destroy cancels subscription" do -    customer = stub_customer -    login customer.user -    result = Braintree::Subscription.create plan_id: 'my_plan', -      payment_method_token: customer.braintree_customer.credit_cards.first.token -    subscription = result.subscription -    delete :destroy, id: subscription.id, user_id: customer.user.id -    assert_equal "Canceled", Braintree::Subscription.find(subscription.id).status +  def setup +    FakeBraintree.activate!    end + +  def teardown +    FakeBraintree.clear! +  end + +  test "get all subscriptions when the user doesn't have an active subscription" do +    user = find_record :user +    login user +    plans = [stub(:id => 1, :name => "First Plan", :price => 10), stub(:id => 2, :name => "Other Plan", :price => 30)] +    Braintree::Plan.expects(:all).returns(plans) + +    get :index + +    assert assigns(:subscriptions) +    assert_response :success +  end + +  test "get subscriptions when user has an active subscription" do +    user = find_record :user +    login user +    plans = [stub(:id => 1, :name => "First Plan", :price => 10), stub(:id => 2, :name => "Other Plan", :price => 30)] +    Braintree::Plan.expects(:all).returns(plans) +    result = Braintree::Subscription.create(payment_method_token: 'user_token', plan_id: 1) +    user.subscription_id = result.subscription.id + +    get :index + +    assert assigns(:subscription) +    assert assigns(:plan) +    assert_response :success +  end + +  test "subscriptions show" do +    user = find_record :user +    login user +    plans = [stub(:id => "1", :name => "First Plan", :price => 10), stub(:id => "2", :name => "Other Plan", :price => 30)] +    Braintree::Plan.expects(:all).returns(plans) + +    get :show, :id => "1" + +    assert assigns(:plan) +    assert_response :success +  end + + test "subscribe creates subscription" do +   user = find_record :user +   user.expects(:save).returns(true) +   login user +   payment_methods = [stub(:token => 'user_token')] +   Braintree::Customer.any_instance.stubs(:payment_methods).returns(payment_methods) +   user.expects(:save).returns(true) + +   post :subscribe, :id => "1", :first_name => "Test", :last_name => "Testing", :company => "RGSoC", :email => "any@email.com", :phone => "555-888-1234" + +   assert assigns(:result).success? +   assert_not_nil flash[:success] + end + + test "unsubscribe cancels subscription" do +   user = find_record :user +   user.expects(:save).returns(true) +   result = Braintree::Subscription.create(payment_method_token: 'user_token', plan_id: '1') +   user.subscription_id = result.subscription.id +   login user + +   delete :unsubscribe, :id => "1" + +   assert assigns(:result).success? +   assert_not_nil flash[:success] +  end +  end diff --git a/engines/billing/test/test_helper.rb b/engines/billing/test/test_helper.rb index 57cdd63..14e453e 100644 --- a/engines/billing/test/test_helper.rb +++ b/engines/billing/test/test_helper.rb @@ -2,6 +2,7 @@  ENV["RAILS_ENV"] = "test"  require "rails/test_help" +require 'mocha/setup'  Rails.backtrace_cleaner.remove_silencers! diff --git a/engines/billing/test/unit/customer_test.rb b/engines/billing/test/unit/customer_test.rb deleted file mode 100644 index 6156f87..0000000 --- a/engines/billing/test/unit/customer_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'test_helper' - -class CustomerTest < ActiveSupport::TestCase -  include StubRecordHelper - -  setup do -    @user = find_record :user -    @customer = FactoryGirl.build(:customer, user: @user) -  end - -  test "test set of attributes should be valid" do -    @customer.valid? -    assert_equal Hash.new, @customer.errors.messages -  end - -  test "customer belongs to user" do -    assert_equal User, @customer.user.class -  end - -  test "user validation" do -    @customer.user = nil -    assert !@customer.valid? -  end - -  test "has no payment info" do -    assert !@customer.braintree_customer_id -    assert !@customer.has_payment_info? -  end - -  test "with no braintree data" do -    assert_equal @customer, @customer.with_braintree_data! -  end - -  test "without default credit card" do -    assert_nil @customer.default_credit_card -  end - -end diff --git a/engines/billing/test/unit/customer_with_payment_info_test.rb b/engines/billing/test/unit/customer_with_payment_info_test.rb deleted file mode 100644 index 0589a59..0000000 --- a/engines/billing/test/unit/customer_with_payment_info_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'test_helper' -require 'fake_braintree' - -class CustomerWithPaymentInfoTest < ActiveSupport::TestCase -  include StubRecordHelper - -  setup do -    @user = find_record :user -    @customer = FactoryGirl.build(:customer_with_payment_info, user: @user) -  end - -  test "has payment_info" do -    assert @customer.braintree_customer_id -    assert @customer.has_payment_info? -  end - -  test "constructs customer with braintree data" do -    @customer.with_braintree_data! -    assert_equal 'Big', @customer.first_name -    assert_equal 'Spender', @customer.last_name -    assert_equal 1, @customer.credit_cards.size -    assert_equal Hash.new, @customer.custom_fields -  end - -  test "can access braintree_customer after reload" do -    @customer.save -    @customer = Customer.find_by_user_id(@customer.user_id) -    @customer.with_braintree_data! -    assert_equal 'Big', @customer.first_name -    assert_equal 'Spender', @customer.last_name -    assert_equal 1, @customer.credit_cards.size -    assert_equal Hash.new, @customer.custom_fields -    @customer.destroy -  end - -  test "sets default_credit_card" do -    @customer.with_braintree_data! -    assert_equal @customer.credit_cards.first, @customer.default_credit_card -  end -end diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb index 35887cc..950a395 100644 --- a/test/support/browser_integration_test.rb +++ b/test/support/browser_integration_test.rb @@ -30,7 +30,6 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest    Capybara.javascript_driver = :poltergeist    Capybara.default_wait_time = 5 -    # Make the Capybara DSL available    include Capybara::DSL | 
