dune-balanced-bankaccount 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +5 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +59 -0
  10. data/Rakefile +34 -0
  11. data/app/assets/images/dune/balanced/bankaccount/.gitkip +0 -0
  12. data/app/assets/javascripts/dune-balanced-bankaccount.js +1 -0
  13. data/app/assets/javascripts/dune/balanced/bankaccount/.gitkip +0 -0
  14. data/app/assets/javascripts/dune/balanced/bankaccount/accounts.js.coffee.erb +24 -0
  15. data/app/assets/javascripts/dune/balanced/bankaccount/flash.js.coffee +16 -0
  16. data/app/assets/javascripts/dune/balanced/bankaccount/form.js.coffee.erb +90 -0
  17. data/app/assets/javascripts/dune/balanced/bankaccount/payments.js.coffee.erb +25 -0
  18. data/app/assets/stylesheets/dune/balanced/bankaccount/.gitkip +0 -0
  19. data/app/controllers/dune/balanced/bankaccount/accounts_controller.rb +67 -0
  20. data/app/controllers/dune/balanced/bankaccount/application_controller.rb +4 -0
  21. data/app/controllers/dune/balanced/bankaccount/confirmations_controller.rb +66 -0
  22. data/app/controllers/dune/balanced/bankaccount/payments_controller.rb +65 -0
  23. data/app/controllers/dune/balanced/bankaccount/routing_numbers_controller.rb +10 -0
  24. data/app/helpers/application_helper.rb +8 -0
  25. data/app/models/dune/balanced/bankaccount/delayed_payment.rb +18 -0
  26. data/app/models/dune/balanced/bankaccount/delayed_payment_processing.rb +22 -0
  27. data/app/models/dune/balanced/bankaccount/payment.rb +69 -0
  28. data/app/models/dune/balanced/bankaccount/payment_base.rb +44 -0
  29. data/app/models/dune/balanced/bankaccount/payment_generator.rb +38 -0
  30. data/app/models/dune/balanced/bankaccount/routing_number.rb +6 -0
  31. data/app/models/dune/balanced/bankaccount/transaction_additional_fee_calculator.rb +12 -0
  32. data/app/models/dune/balanced/bankaccount/transaction_fee_calculator_base.rb +34 -0
  33. data/app/models/dune/balanced/bankaccount/transaction_inclusive_fee_calculator.rb +16 -0
  34. data/app/models/dune/balanced/bankaccount/verification.rb +34 -0
  35. data/app/models/dune/balanced/event_registered.rb +13 -0
  36. data/app/views/dune/balanced/bankaccount/accounts/new.html.slim +96 -0
  37. data/app/views/dune/balanced/bankaccount/confirmations/new.html.slim +53 -0
  38. data/app/views/dune/balanced/bankaccount/payments/new.html.slim +97 -0
  39. data/app/views/notifications_mailer/balanced/bankaccount/bank_account_replaced.en.html.slim +12 -0
  40. data/app/views/notifications_mailer/balanced/bankaccount/confirm_bank_account.en.html.slim +16 -0
  41. data/app/views/notifications_mailer/balanced/bankaccount/new_verification_started.en.html.slim +14 -0
  42. data/app/views/notifications_mailer/subjects/balanced/bankaccount/bank_account_replaced.en.text.slim +1 -0
  43. data/app/views/notifications_mailer/subjects/balanced/bankaccount/confirm_bank_account.en.text.slim +1 -0
  44. data/app/views/notifications_mailer/subjects/balanced/bankaccount/new_verification_started.en.text.slim +1 -0
  45. data/app/workers/dune/balanced/bankaccount/debit_authorized_contributions_worker.rb +26 -0
  46. data/bin/rails +8 -0
  47. data/bin/test_suite +19 -0
  48. data/config/environment.rb +0 -0
  49. data/config/initializers/event_observers.rb +2 -0
  50. data/config/initializers/registration.rb +5 -0
  51. data/config/locales/en.yml +59 -0
  52. data/config/locales/simple_form.en.yml +21 -0
  53. data/config/routes.rb +6 -0
  54. data/db/migrate/20140224220426_create_routing_number.rb +10 -0
  55. data/db/migrate/20140813180429_use_hrefs_for_balanced_resources.rb +41 -0
  56. data/dune-balanced-bankaccount.gemspec +29 -0
  57. data/lib/dune/balanced/bankaccount.rb +11 -0
  58. data/lib/dune/balanced/bankaccount/engine.rb +14 -0
  59. data/lib/dune/balanced/bankaccount/interface.rb +28 -0
  60. data/lib/dune/balanced/bankaccount/version.rb +7 -0
  61. data/lib/float_extensions.rb +11 -0
  62. data/lib/tasks/dune/balanced/bankaccount/update_routing_numbers.rake +27 -0
  63. data/spec/controllers/dune/balanced/bankaccount/accounts_controller_spec.rb +183 -0
  64. data/spec/controllers/dune/balanced/bankaccount/confirmations_controller_spec.rb +190 -0
  65. data/spec/controllers/dune/balanced/bankaccount/payments_controller_spec.rb +158 -0
  66. data/spec/controllers/dune/balanced/bankaccount/routing_numbers_controller_spec.rb +22 -0
  67. data/spec/factories.rb +47 -0
  68. data/spec/lib/dune/balanced/bankaccount/interface_spec.rb +37 -0
  69. data/spec/models/dune/balanced/bankaccount/delayed_payment_processing_spec.rb +38 -0
  70. data/spec/models/dune/balanced/bankaccount/delayed_payment_spec.rb +66 -0
  71. data/spec/models/dune/balanced/bankaccount/payment_generator_spec.rb +65 -0
  72. data/spec/models/dune/balanced/bankaccount/payment_spec.rb +252 -0
  73. data/spec/models/dune/balanced/bankaccount/routing_number_spec.rb +6 -0
  74. data/spec/models/dune/balanced/bankaccount/transaction_additional_fee_calculator_spec.rb +53 -0
  75. data/spec/models/dune/balanced/bankaccount/transaction_inclusive_fee_calculator_spec.rb +34 -0
  76. data/spec/models/dune/balanced/bankaccount/verification_spec.rb +34 -0
  77. data/spec/models/dune/balanced/event_registered_spec.rb +51 -0
  78. data/spec/spec_helper.rb +44 -0
  79. data/spec/workers/dune/balanced/bankaccount/debit_authorized_contributions_worker_spec.rb +71 -0
  80. metadata +241 -0
@@ -0,0 +1,65 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class PaymentsController < AccountsController
3
+ def create
4
+ attach_bank_to_customer
5
+ update_customer
6
+
7
+ payment = Dune::Balanced::Bankaccount::PaymentGenerator.new(
8
+ customer,
9
+ resource,
10
+ resource_params
11
+ )
12
+ payment.complete
13
+
14
+ redirect_to(*checkout_response_params(payment.status))
15
+ end
16
+
17
+ def update_customer
18
+ Dune::Balanced::Customer.new(current_user, params).update!
19
+ end
20
+
21
+ protected
22
+ def resource
23
+ @resource ||= if params[:payment][:match_id].present?
24
+ Match.find(params[:payment].fetch(:match_id))
25
+ else
26
+ Contribution.find(params[:payment].fetch(:contribution_id))
27
+ end
28
+ end
29
+
30
+ def resource_name
31
+ resource.class.model_name.singular.to_sym
32
+ end
33
+
34
+ def checkout_response_params(status)
35
+ route_params = [resource.project.permalink, resource.id]
36
+
37
+ {
38
+ contribution: {
39
+ succeeded: [
40
+ main_app.project_contribution_path(*route_params)
41
+ ],
42
+ pending: [
43
+ main_app.project_contribution_path(*route_params)
44
+ ],
45
+ failed: [
46
+ main_app.edit_project_contribution_path(*route_params),
47
+ alert: t('.errors.default')
48
+ ]
49
+ },
50
+ match: {
51
+ succeeded: [
52
+ main_app.project_match_path(*route_params)
53
+ ],
54
+ pending: [
55
+ main_app.project_match_path(*route_params)
56
+ ],
57
+ failed: [
58
+ main_app.edit_project_match_path(*route_params),
59
+ alert: t('.errors.default')
60
+ ]
61
+ }
62
+ }.fetch(resource_name).fetch(status)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,10 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class RoutingNumbersController < ApplicationController
3
+
4
+ def show
5
+ routing_number = RoutingNumber.where(number: params[:id]).first
6
+ render json: { ok: routing_number.present?, bank_name: routing_number.try(:bank_name) }
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module ApplicationHelper
2
+ def method_missing(method, *args, &block)
3
+ return main_app.send(method, *args) if (method.to_s.end_with?('_path') ||
4
+ method.to_s.end_with?('_url')) &&
5
+ main_app.respond_to?(method)
6
+ super
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class DelayedPayment < PaymentBase
3
+ def checkout!
4
+ resource.wait_confirmation!
5
+
6
+ @status = :succeeded
7
+ resource.update_attributes(
8
+ payment_method: engine_name,
9
+ payment_service_fee: fee_calculator.fees,
10
+ payment_service_fee_paid_by_user: attrs[:pay_fee]
11
+ )
12
+ end
13
+
14
+ def successful?
15
+ @status.eql? :succeeded
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class DelayedPaymentProcessing
3
+ def initialize(contributor, resources)
4
+ @contributor, @resources = contributor, resources
5
+ end
6
+
7
+ def complete
8
+ @resources.each do |resource|
9
+ Dune::Balanced::Bankaccount::Payment.new(
10
+ 'balanced-bankaccount',
11
+ customer,
12
+ resource,
13
+ {}
14
+ ).checkout!
15
+ end
16
+ end
17
+
18
+ def customer
19
+ @customer ||= ::Balanced::Customer.find(@contributor.href)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,69 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class Payment < PaymentBase
3
+ def checkout!
4
+ perform_debit!
5
+ resource.confirm
6
+ rescue Balanced::BadRequest
7
+ @status = :failed
8
+ resource.cancel
9
+ ensure
10
+ resource.update_attributes(
11
+ payment_id: @debit.try(:id),
12
+ payment_method: engine_name,
13
+ payment_service_fee: fee_calculator.fees,
14
+ payment_service_fee_paid_by_user: attrs[:pay_fee]
15
+ )
16
+ update_meta(@debit) if @debit
17
+ end
18
+
19
+ def successful?
20
+ %i(pending succeeded).include? status
21
+ end
22
+
23
+ def debit_resource_href
24
+ attrs.fetch(:use_bank) { contributor.bank_account_href }
25
+ end
26
+
27
+ def contributor
28
+ @contributor ||= Dune::Balanced::Contributor.find_by(href: @customer.href)
29
+ end
30
+
31
+ private
32
+
33
+ def perform_debit!
34
+ debit_params = {
35
+ amount: amount_in_cents,
36
+ appears_on_statement_as: ::Configuration[:balanced_appears_on_statement_as],
37
+ description: debit_description,
38
+ meta: meta,
39
+ source: bank_account
40
+ }
41
+
42
+ order = Dune::Balanced::OrderProxy.new(resource.project)
43
+ @debit = order.debit_from(debit_params)
44
+ end
45
+
46
+ def update_meta(debit)
47
+ debit.meta = meta
48
+ debit.save
49
+ end
50
+
51
+ def resource_name
52
+ resource.class.model_name.singular
53
+ end
54
+
55
+ def debit_description
56
+ I18n.t('description',
57
+ project_name: resource.try(:project).try(:name),
58
+ scope: "dune.balanced.bankaccount.payments.debit.#{resource_name}")
59
+ end
60
+
61
+ def bank_account
62
+ ::Balanced::BankAccount.find(debit_resource_href)
63
+ end
64
+
65
+ def meta
66
+ PayableResourceSerializer.new(resource).to_json
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,44 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class PaymentBase
3
+ attr_reader :engine_name, :resource, :attrs
4
+
5
+ def initialize(engine_name, customer, resource, attrs = {})
6
+ @engine_name = engine_name
7
+ @customer = customer
8
+ @resource = resource
9
+ @attrs = attrs
10
+ end
11
+
12
+ def amount_in_cents
13
+ (fee_calculator.gross_amount * 100).round
14
+ end
15
+
16
+ def fee_calculator
17
+ @fee_calculator and return @fee_calculator
18
+
19
+ calculator_class = if ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include? attrs[:pay_fee]
20
+ TransactionAdditionalFeeCalculator
21
+ else
22
+ TransactionInclusiveFeeCalculator
23
+ end
24
+
25
+ @fee_calculator = calculator_class.new(resource.value)
26
+ end
27
+
28
+ def debit
29
+ @debit.try(:sanitize)
30
+ end
31
+
32
+ def status
33
+ @debit.try(:status).try(:to_sym) || @status
34
+ end
35
+
36
+ def checkout!
37
+ raise NotImplementedError
38
+ end
39
+
40
+ def successful?
41
+ raise NotImplementedError
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class PaymentGenerator
3
+ attr_reader :attrs, :resource, :customer
4
+
5
+ delegate :status, to: :payment
6
+
7
+ def initialize(customer, resource, attrs = {})
8
+ @customer = customer
9
+ @resource = resource
10
+ @attrs = attrs
11
+ end
12
+
13
+ def complete
14
+ payment.checkout!
15
+ end
16
+
17
+ def payment
18
+ @payment ||= payment_class.new(
19
+ 'balanced-bankaccount',
20
+ customer,
21
+ resource,
22
+ attrs
23
+ )
24
+ end
25
+
26
+ def payment_class
27
+ @payment_class ||= can_debit_resource? ? Dune::Balanced::Bankaccount::Payment : Dune::Balanced::Bankaccount::DelayedPayment
28
+ end
29
+
30
+ def can_debit_resource?
31
+ debit_resource.bank_account_verifications.to_a.first.try(:verification_status).eql? 'succeeded'
32
+ end
33
+
34
+ def debit_resource
35
+ @debit_resource ||= ::Balanced::BankAccount.find(@attrs.fetch(:use_bank))
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,6 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class RoutingNumber < ActiveRecord::Base
3
+ self.table_name = :routing_numbers
4
+ validates :number, :bank_name, presence: true
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ require 'float_extensions'
2
+
3
+ module Dune::Balanced::Bankaccount
4
+ class TransactionAdditionalFeeCalculator < TransactionFeeCalculatorBase
5
+ using FloatExtensions
6
+
7
+ def net_amount
8
+ transaction_value.to_f.floor_with_two_decimal_places
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,34 @@
1
+ require 'float_extensions'
2
+
3
+ module Dune::Balanced::Bankaccount
4
+ class TransactionFeeCalculatorBase
5
+ using FloatExtensions
6
+
7
+ attr_writer :transaction_value
8
+
9
+ def initialize(transaction_value)
10
+ @transaction_value = transaction_value
11
+ end
12
+
13
+ def transaction_value
14
+ @transaction_value.to_f.floor_with_two_decimal_places
15
+ end
16
+
17
+ def gross_amount
18
+ net_amount + fees
19
+ end
20
+
21
+ def net_amount
22
+ raise NotImplementedError
23
+ end
24
+
25
+ # Base calculation of fees
26
+ # 1% + 30¢ ($5 cap)
27
+ def fees
28
+ [
29
+ (net_amount * 0.01 + 0.3).ceil_with_two_decimal_places,
30
+ 5.0
31
+ ].min
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ require 'float_extensions'
2
+
3
+ module Dune::Balanced::Bankaccount
4
+ class TransactionInclusiveFeeCalculator < TransactionFeeCalculatorBase
5
+ using FloatExtensions
6
+
7
+ # Base calculation of fees
8
+ # 1% + 30¢ ($5 cap)
9
+ def net_amount
10
+ [
11
+ ((transaction_value - 0.3) / 1.01).floor_with_two_decimal_places,
12
+ transaction_value - 5
13
+ ].max
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ module Dune::Balanced::Bankaccount
2
+ class Verification
3
+ delegate :user, to: :contributor
4
+
5
+ def self.find(href)
6
+ new(::Balanced::BankAccountVerification.find(href))
7
+ end
8
+
9
+ def initialize(balanced_verification)
10
+ @source = balanced_verification
11
+ end
12
+
13
+ def bank_account_href
14
+ bank_account.href
15
+ end
16
+
17
+ def contributor
18
+ Dune::Balanced::Contributor.find_by(bank_account_href: bank_account_href)
19
+ end
20
+
21
+ def confirm
22
+ DebitAuthorizedContributionsWorker.perform_async(contributor.id)
23
+ end
24
+
25
+ # Delegate instance methods to Balanced::BankAccountVerification object
26
+ def method_missing(method, *args, &block)
27
+ if @source.respond_to? method
28
+ @source.public_send(method, *args, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ module Dune::Balanced
2
+ class EventRegistered
3
+ def confirm(event)
4
+ case event.type
5
+ when 'bank_account_verification.deposited'
6
+ Notification.notify('balanced/bankaccount/confirm_bank_account', event.user)
7
+ when 'bank_account_verification.verified'
8
+ verification = Dune::Balanced::Bankaccount::Verification.find(event.entity_href)
9
+ verification.confirm
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,96 @@
1
+ = initjs_tag app_name: 'Dune', partial: true
2
+
3
+ .dune-balanced-bankaccount-form[data-balanced-bankaccount-form data-routing-number-path=routing_number_path(:id)]
4
+ = simple_form_for :payment, url: accounts_path do |f|
5
+ = f.input :use_bank, as: :hidden
6
+ .row
7
+ .large-12.columns.title
8
+ h3
9
+ .row
10
+ .large-4.columns
11
+ = t('.bankaccount.title')
12
+ .large-8.columns.text-right
13
+ small
14
+ - if @bank_account.present?
15
+ = t('.status')
16
+ | &nbsp;
17
+ - if @bank_account.bank_account_verifications.to_a.first.try(:verification_status) == 'succeeded'
18
+ span.label = t('.account_verified')
19
+ - else
20
+ span.label.alert = t('.account_unverified')
21
+ | &nbsp;
22
+ = link_to t('.verify'), new_confirmation_path
23
+ - if @bank_account
24
+ p = t('.bankaccount.subtitle')
25
+
26
+ .row
27
+ .large-8.columns
28
+ - if @bank_account
29
+ section.use-existing-item.row
30
+ .large-12.columns
31
+ .bank-box
32
+ label.row
33
+ dl
34
+ .large-1.columns
35
+ = f.radio_button :show_bank, 'existing', { class: 'show-bank', checked: false, label: false }
36
+ .large-5.columns
37
+ dt.hide
38
+ dd = @bank_account.account_number
39
+ .large-6.columns.bank_name
40
+ dt.hine
41
+ dd = @bank_account.bank_name
42
+ .expandable.hide
43
+ = f.simple_fields_for :current do |fc|
44
+ .row
45
+ .large-8.columns
46
+ = fc.input :account_holder_name, input_html: { value: @bank_account.try(:name) }
47
+
48
+ .large-4.columns
49
+ = fc.input :account_type, as: :radio_buttons, collection: [['Checking', 'checking'], ['Savings', 'savings']], checked: (@bank_account.try(:account_type) || 'checking')
50
+
51
+ .row
52
+ .large-3.columns
53
+ = fc.input :routing_number, input_html: { value: @bank_account.try(:routing_number) }
54
+
55
+ .large-4.columns
56
+ = fc.input :account_number, input_html: { value: @bank_account.try(:account_number) }
57
+
58
+ .large-5.columns
59
+ = fc.input :bank_name, disabled: true, input_html: { value: @bank_account.try(:bank_name) }
60
+
61
+ section.row
62
+ .large-12.columns
63
+ .bank-box[class="#{'new-bank' unless @bank_account.present?}"]
64
+ .row
65
+ - if @bank_account.present?
66
+ label
67
+ .large-1.columns
68
+ = f.radio_button :show_bank, 'new', { class: 'show-bank', checked: false, label: false }
69
+ .large-8.columns
70
+ = t('.replace_bank')
71
+ .large-3.columns.bank_name
72
+
73
+ .add-new-bankaccount-form.expandable[class="#{'hide with-other-bank' if @bank_account.present?}"]
74
+ .large-12.columns.check-numbers
75
+ = image_tag('payments/check-numbers.png')
76
+ .large-12.columns
77
+ .row
78
+ .large-8.columns
79
+ = f.input :account_holder_name, required: true, input_html: { 'data-balanced-bank-account-input' => '' }
80
+
81
+ .large-4.columns
82
+ = f.input :account_type, as: :radio_buttons, checked: 'checking', required: true, collection: [['Checking', 'checking'], ['Savings', 'savings']]
83
+
84
+ .row
85
+ .large-3.columns
86
+ = f.input :routing_number, required: true, input_html: { 'data-balanced-bank-account-input' => '' }
87
+
88
+ .large-4.columns
89
+ = f.input :account_number, required: true, input_html: { 'data-balanced-bank-account-input' => '' }
90
+
91
+ .large-5.columns
92
+ = f.input :bank_name, disabled: true, input_html: { 'data-balanced-bank-account-input' => '' }
93
+
94
+ .large-12.columns.submit
95
+ = f.button :submit, (@bank_account.present? ? t('.replace') : t('.add')), data: { disable_with: t('.proccessing'), enable_with: (@bank_account.present? ? t('.replace') : t('.add')) }
96
+