dune-balanced 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +13 -0
  6. data/CHANGELOG.md +5 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +125 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +63 -0
  11. data/Rakefile +34 -0
  12. data/app/controllers/dune/balanced/notifications_controller.rb +11 -0
  13. data/app/decorators/dune/balanced/customer_decorator.rb +14 -0
  14. data/app/models/dune/balanced/.gitkip +0 -0
  15. data/app/models/dune/balanced/contributor.rb +11 -0
  16. data/app/models/dune/balanced/customer.rb +56 -0
  17. data/app/models/dune/balanced/event.rb +101 -0
  18. data/app/models/dune/balanced/order.rb +9 -0
  19. data/app/models/dune/balanced/order_proxy.rb +55 -0
  20. data/app/models/dune/balanced/payout.rb +77 -0
  21. data/app/models/dune/balanced/refund.rb +53 -0
  22. data/app/models/dune/balanced/user.rb +6 -0
  23. data/app/observers/dune/balanced/events_observer/debit_canceled.rb +13 -0
  24. data/bin/rails +8 -0
  25. data/config/initializers/balanced.rb +2 -0
  26. data/config/initializers/events_observer.rb +2 -0
  27. data/config/locales/en.yml +9 -0
  28. data/config/routes.rb +4 -0
  29. data/db/migrate/20140211203335_create_balanced_contributors.rb +10 -0
  30. data/db/migrate/20140324175041_add_bank_account_uri_to_balanced_contributors.rb +5 -0
  31. data/db/migrate/20140817195359_create_dune_balanced_orders.rb +10 -0
  32. data/dune-balanced.gemspec +32 -0
  33. data/lib/dune/balanced.rb +9 -0
  34. data/lib/dune/balanced/engine.rb +13 -0
  35. data/lib/dune/balanced/version.rb +5 -0
  36. data/spec/controllers/dune/balanced/notifications_controller_spec.rb +61 -0
  37. data/spec/decorators/dune/balanced/customer_decorator_spec.rb +19 -0
  38. data/spec/fixtures/notifications/bank_account_verification.deposited.yml +48 -0
  39. data/spec/fixtures/notifications/bank_account_verification.verified.yml +48 -0
  40. data/spec/fixtures/notifications/debit.canceled.yml +74 -0
  41. data/spec/fixtures/notifications/debit.created.yml +72 -0
  42. data/spec/fixtures/notifications/debit.succeeded.yml +72 -0
  43. data/spec/models/dune/balanced/customer_spec.rb +73 -0
  44. data/spec/models/dune/balanced/event_spec.rb +219 -0
  45. data/spec/models/dune/balanced/order_proxy_spec.rb +87 -0
  46. data/spec/models/dune/balanced/order_spec.rb +8 -0
  47. data/spec/models/dune/balanced/payout_spec.rb +104 -0
  48. data/spec/models/dune/balanced/refund_spec.rb +157 -0
  49. data/spec/observers/dune/balanced/events_observer/debit_canceled_spec.rb +32 -0
  50. data/spec/spec_helper.rb +18 -0
  51. data/spec/support/fixtures_test_helper.rb +11 -0
  52. metadata +223 -0
@@ -0,0 +1,9 @@
1
+ module Dune::Balanced
2
+ class Order < ActiveRecord::Base
3
+ self.table_name = :dune_balanced_orders
4
+
5
+ belongs_to :project
6
+
7
+ validates :project, :href, presence: true
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ module Dune::Balanced
2
+ class OrderProxy
3
+ I18N_SCOPE = 'dune.balanced.order'
4
+
5
+ delegate :user, to: :project
6
+ delegate :amount, :amount_escrowed, :debit_from, :description, :meta,
7
+ :reload, :save, to: :order
8
+
9
+ attr_reader :project
10
+
11
+ def initialize(project)
12
+ @project = project
13
+ end
14
+
15
+ private
16
+
17
+ def order
18
+ @order ||= if order_href
19
+ ::Balanced::Order.find(order_href)
20
+ else
21
+ create_order
22
+ end
23
+ end
24
+
25
+ def order_href
26
+ @order_href ||= Order.find_by(project_id: project).try(:href)
27
+ end
28
+
29
+ def create_order
30
+ subject = Customer.new(user, {}).fetch.create_order
31
+ Order.create!(href: subject.href, project: project)
32
+
33
+ subject.description = I18n.t('description',
34
+ project_id: project.id,
35
+ project_name: project.name,
36
+ scope: I18N_SCOPE
37
+ )
38
+
39
+ project_url = Rails.application.routes.url_helpers.project_url(project)
40
+ subject.meta = {
41
+ 'Project' => project.name,
42
+ 'Goal' => project.goal,
43
+ 'Campaign Type' => project.campaign_type.humanize,
44
+ 'User' => project.user.name,
45
+ 'Category' => project.category.name_en,
46
+ 'URL' => project_url,
47
+ 'Expires At' => I18n.l(project.expires_at),
48
+ 'ID' => project.id,
49
+ }
50
+ subject.save
51
+
52
+ subject
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,77 @@
1
+ module Dune::Balanced
2
+ class Error < StandardError; end
3
+ class NoBankAccount < Error; end
4
+
5
+ class Payout
6
+ def initialize(project, requestor_user)
7
+ @project = project
8
+ @requestor = requestor_user
9
+ end
10
+
11
+ def complete!(bank_account_href = nil)
12
+ credit_project_owner!(bank_account_href)
13
+ credit_platform!
14
+ end
15
+
16
+ def customer
17
+ dune_customer.fetch
18
+ end
19
+
20
+ private
21
+
22
+ def credit_project_owner!(bank_account_href = nil)
23
+ to_be_credited = if bank_account_href
24
+ Balanced::BankAccount.find(bank_account_href)
25
+ else
26
+ customer.bank_accounts.first
27
+ end
28
+
29
+ credit!(to_be_credited, financials.net_amount)
30
+ end
31
+
32
+ def credit_platform!
33
+ to_be_credited =
34
+ Balanced::Marketplace.mine.owner_customer.bank_accounts.first
35
+ credit!(to_be_credited, financials.platform_fee)
36
+ end
37
+
38
+ def order
39
+ @order ||= begin
40
+ href = Dune::Balanced::Order
41
+ .find_by(project_id: @project.id).href
42
+ Balanced::Order.find(href)
43
+ end
44
+ end
45
+
46
+ def financials
47
+ @financials ||= ProjectFinancialByService
48
+ .new(p, %w(balanced-bankaccount balanced-creditcard))
49
+ end
50
+
51
+ def dune_customer
52
+ @dune_customer ||= Dune::Balanced::Customer.new(@project.user, {})
53
+ end
54
+
55
+ def credit!(bank_account, amount)
56
+ if bank_account.blank?
57
+ raise NoBankAccount, 'The customer doesn\'t have a bank account to credit.'
58
+ end
59
+
60
+ order.credit_to(
61
+ amount: in_cents(amount),
62
+ destination: bank_account,
63
+ )
64
+ ::Payout.create(
65
+ bank_account_href: bank_account.href,
66
+ payment_service: 'balanced',
67
+ project_id: @project.id,
68
+ user_id: @requestor.id,
69
+ value: amount,
70
+ )
71
+ end
72
+
73
+ def in_cents(amount)
74
+ (amount * 100).round
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,53 @@
1
+ module Dune::Balanced
2
+ class Refund
3
+ FIXED_OPERATIONAL_FEE = 0.3
4
+
5
+ attr_reader :paid_resource
6
+
7
+ def initialize(paid_resource)
8
+ @paid_resource = paid_resource
9
+ end
10
+
11
+ def complete!(reason, amount = paid_resource.value)
12
+ unless amount.zero?
13
+ refund_amount = ((amount + refundable_fees(amount)) * 100).round
14
+ debit.refund(
15
+ amount: refund_amount,
16
+ description: I18n.t('dune.balanced.refund.description',
17
+ resource_id: paid_resource.id,
18
+ resource_name: paid_resource.class.model_name.human
19
+ ),
20
+ meta: {
21
+ 'reason' => I18n.t("dune.balanced.refund_reasons.#{reason}")
22
+ }
23
+ )
24
+ end
25
+ paid_resource.refund!
26
+ end
27
+
28
+ def debit
29
+ @debit ||= ::Balanced::Debit.find("/debits/#{paid_resource.payment_id}")
30
+ end
31
+
32
+ def refundable_fees(refund_amount)
33
+ percentual_fee = if paid_resource.payment_service_fee_paid_by_user
34
+ refund_amount / paid_resource.value * paid_resource.payment_service_fee
35
+ else
36
+ 0
37
+ end
38
+
39
+ (percentual_fee - FIXED_OPERATIONAL_FEE).round(2)
40
+ end
41
+
42
+ private
43
+
44
+ def resource_amount
45
+ to_be_refunded = if paid_resource.payment_service_fee_paid_by_user
46
+ paid_resource.value + paid_resource.payment_service_fee
47
+ else
48
+ paid_resource.value
49
+ end
50
+ (to_be_refunded * 100 - FIXED_OPERATIONAL_FEE).round
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,6 @@
1
+ module Dune::Balanced::User
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ has_one :balanced_contributor, class_name: 'Dune::Balanced::Contributor'
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module Dune::Balanced
2
+ module EventsObserver
3
+ class DebitCanceled
4
+
5
+ def perform(event)
6
+ if event.type.eql? 'debit.canceled'
7
+ event.resource.cancel!
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/dune/balanced/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'
@@ -0,0 +1,2 @@
1
+ options = Rails.env.development? ? { logging_level: 'INFO' } : {}
2
+ Balanced.configure(::Configuration[:balanced_api_key_secret], options)
@@ -0,0 +1,2 @@
1
+ events_observer_debit_canceled = Dune::Balanced::EventsObserver::DebitCanceled.new
2
+ Dune::Balanced::Event.add_observer(events_observer_debit_canceled, :perform)
@@ -0,0 +1,9 @@
1
+ en:
2
+ dune:
3
+ balanced:
4
+ order:
5
+ description: "Project #%{project_id} %{project_name}"
6
+ refund:
7
+ description: 'Refund for %{resource_name} #%{resource_id}'
8
+ refund_reasons:
9
+ match_automatic: 'Automatic refund done after match finishing without use entire paid value'
@@ -0,0 +1,4 @@
1
+ Dune::Balanced::Engine.routes.draw do
2
+ resources :notifications, only: :create
3
+ end
4
+
@@ -0,0 +1,10 @@
1
+ class CreateBalancedContributors < ActiveRecord::Migration
2
+ def change
3
+ create_table :balanced_contributors do |t|
4
+ t.references :user, index: true
5
+ t.string :uri
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class AddBankAccountUriToBalancedContributors < ActiveRecord::Migration
2
+ def change
3
+ add_column :balanced_contributors, :bank_account_uri, :string
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ class CreateDuneBalancedOrders < ActiveRecord::Migration
2
+ def change
3
+ create_table :dune_balanced_orders do |t|
4
+ t.references :project, index: true, null: false
5
+ t.string :href, null: false
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dune/balanced/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'dune-balanced'
8
+ spec.version = Dune::Balanced::VERSION
9
+ spec.authors = ['Pierro']
10
+ spec.email = %w(legrand.work@gmail.com)
11
+ spec.summary = 'dune integration with Balanced Payments From Neighborly'
12
+ spec.description = 'This is the base to integrate Balanced Payments on dune-investissement'
13
+ spec.homepage = 'https://github.com/FromUte/dune-balanced'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ # faraday_middleware 0.9.1 is raising
22
+ # NoMethodError: undefined method `register_middleware' for #<Faraday::Connection:0x00000002ebc178>
23
+ spec.add_dependency 'faraday', '0.8.9'
24
+ spec.add_dependency 'faraday_middleware', '0.9.0'
25
+
26
+ spec.add_dependency 'balanced', '~> 1.1'
27
+ spec.add_dependency 'draper', '~> 1.3'
28
+ spec.add_dependency 'rails', '~> 4.0'
29
+ spec.add_development_dependency 'rspec-rails', '~> 2.14'
30
+ spec.add_development_dependency 'shoulda-matchers'
31
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
32
+ end
@@ -0,0 +1,9 @@
1
+ require 'dune/balanced/engine'
2
+ require 'dune/balanced/version'
3
+ require 'balanced'
4
+
5
+ module Dune
6
+ module Balanced
7
+ # Your code goes here...
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Dune
2
+ module Balanced
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Dune::Balanced
5
+
6
+ config.autoload_paths += Dir["#{config.root}/app/observers/**/"]
7
+
8
+ config.to_prepare do
9
+ ::User.send(:include, Dune::Balanced::User)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Dune
2
+ module Balanced
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dune::Balanced::NotificationsController do
4
+ routes { Dune::Balanced::Engine.routes }
5
+ let(:event) { double('Event') }
6
+
7
+ shared_examples 'create action' do
8
+ it 'saves a new event' do
9
+ expect_any_instance_of(Dune::Balanced::Event).to receive(:save)
10
+ post :create, params
11
+ end
12
+
13
+ context 'with valid event' do
14
+ before do
15
+ Dune::Balanced::Event.any_instance.stub(:valid?).and_return(true)
16
+ end
17
+
18
+ it 'responds with 200 http status' do
19
+ post :create, params
20
+ expect(response.status).to eql(200)
21
+ end
22
+ end
23
+
24
+ context 'with invalid event' do
25
+ before do
26
+ Dune::Balanced::Event.any_instance.stub(:valid?).and_return(false)
27
+ end
28
+
29
+ it 'responds with 400 http status' do
30
+ post :create, params
31
+ expect(response.status).to eql(400)
32
+ end
33
+ end
34
+ end
35
+
36
+ describe 'POST \'create\'' do
37
+ context 'with debit.created notification' do
38
+ let(:params) do
39
+ attributes_for_notification('debit.created')
40
+ end
41
+
42
+ it_behaves_like 'create action'
43
+ end
44
+
45
+ context 'with debit.succeeded notification' do
46
+ let(:params) do
47
+ attributes_for_notification('debit.succeeded')
48
+ end
49
+
50
+ it_behaves_like 'create action'
51
+ end
52
+
53
+ context 'with bank_account_verification.deposited notification' do
54
+ let(:params) do
55
+ attributes_for_notification('bank_account_verification.deposited')
56
+ end
57
+
58
+ it_behaves_like 'create action'
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dune::Balanced::CustomerDecorator do
4
+ let(:remote_source) { double('Balanced::Customer') }
5
+ let(:source) do
6
+ double('Dune::Balanced::Customer',fetch: remote_source)
7
+ end
8
+ subject { described_class.new(source) }
9
+
10
+ describe 'bank account name' do
11
+ it 'gets this info fetching the bank accout of the customer' do
12
+ bank_account = double('Balanced::BankAccount',
13
+ bank_name: 'JPMORGAN CHASE BANK',
14
+ account_number: 'xxxxxx0002')
15
+ remote_source.stub(:bank_accounts).and_return([bank_account])
16
+ expect(subject.bank_account_name).to eql('JPMORGAN CHASE BANK xxxxxx0002')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ ---
2
+ events:
3
+ - links: {}
4
+ occurred_at: '2014-08-14T18:58:44.850000Z'
5
+ uri: "/events/EV048db6b423e511e4b0ef0647853a3607"
6
+ entity:
7
+ bank_account_verifications:
8
+ - verification_status: pending
9
+ links:
10
+ bank_account: BA7AA3yiW6upqETZwU8pyqYg
11
+ created_at: '2014-08-14T18:58:44.399925Z'
12
+ attempts_remaining: 3
13
+ updated_at: '2014-08-14T18:58:44.850225Z'
14
+ deposit_status: succeeded
15
+ attempts: 0
16
+ href: "/verifications/BZ6V6wsEkufWOpfszp360nj"
17
+ meta: {}
18
+ id: BZ6V6wsEkufWOpfszp360nj
19
+ links:
20
+ bank_account_verifications.bank_account: "/bank_accounts/{bank_account_verifications.bank_account}"
21
+ href: "/events/EV048db6b423e511e4b0ef0647853a3607"
22
+ type: bank_account_verification.deposited
23
+ id: EV048db6b423e511e4b0ef0647853a3607
24
+ links: {}
25
+ registration:
26
+ events:
27
+ - links: {}
28
+ occurred_at: '2014-08-14T18:58:44.850000Z'
29
+ uri: "/events/EV048db6b423e511e4b0ef0647853a3607"
30
+ entity:
31
+ bank_account_verifications:
32
+ - verification_status: pending
33
+ links:
34
+ bank_account: BA7AA3yiW6upqETZwU8pyqYg
35
+ created_at: '2014-08-14T18:58:44.399925Z'
36
+ attempts_remaining: 3
37
+ updated_at: '2014-08-14T18:58:44.850225Z'
38
+ deposit_status: succeeded
39
+ attempts: 0
40
+ href: "/verifications/BZ6V6wsEkufWOpfszp360nj"
41
+ meta: {}
42
+ id: BZ6V6wsEkufWOpfszp360nj
43
+ links:
44
+ bank_account_verifications.bank_account: "/bank_accounts/{bank_account_verifications.bank_account}"
45
+ href: "/events/EV048db6b423e511e4b0ef0647853a3607"
46
+ type: bank_account_verification.deposited
47
+ id: EV048db6b423e511e4b0ef0647853a3607
48
+ links: {}