dune-balanced 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: {}