dune-balanced 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +125 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +34 -0
- data/app/controllers/dune/balanced/notifications_controller.rb +11 -0
- data/app/decorators/dune/balanced/customer_decorator.rb +14 -0
- data/app/models/dune/balanced/.gitkip +0 -0
- data/app/models/dune/balanced/contributor.rb +11 -0
- data/app/models/dune/balanced/customer.rb +56 -0
- data/app/models/dune/balanced/event.rb +101 -0
- data/app/models/dune/balanced/order.rb +9 -0
- data/app/models/dune/balanced/order_proxy.rb +55 -0
- data/app/models/dune/balanced/payout.rb +77 -0
- data/app/models/dune/balanced/refund.rb +53 -0
- data/app/models/dune/balanced/user.rb +6 -0
- data/app/observers/dune/balanced/events_observer/debit_canceled.rb +13 -0
- data/bin/rails +8 -0
- data/config/initializers/balanced.rb +2 -0
- data/config/initializers/events_observer.rb +2 -0
- data/config/locales/en.yml +9 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20140211203335_create_balanced_contributors.rb +10 -0
- data/db/migrate/20140324175041_add_bank_account_uri_to_balanced_contributors.rb +5 -0
- data/db/migrate/20140817195359_create_dune_balanced_orders.rb +10 -0
- data/dune-balanced.gemspec +32 -0
- data/lib/dune/balanced.rb +9 -0
- data/lib/dune/balanced/engine.rb +13 -0
- data/lib/dune/balanced/version.rb +5 -0
- data/spec/controllers/dune/balanced/notifications_controller_spec.rb +61 -0
- data/spec/decorators/dune/balanced/customer_decorator_spec.rb +19 -0
- data/spec/fixtures/notifications/bank_account_verification.deposited.yml +48 -0
- data/spec/fixtures/notifications/bank_account_verification.verified.yml +48 -0
- data/spec/fixtures/notifications/debit.canceled.yml +74 -0
- data/spec/fixtures/notifications/debit.created.yml +72 -0
- data/spec/fixtures/notifications/debit.succeeded.yml +72 -0
- data/spec/models/dune/balanced/customer_spec.rb +73 -0
- data/spec/models/dune/balanced/event_spec.rb +219 -0
- data/spec/models/dune/balanced/order_proxy_spec.rb +87 -0
- data/spec/models/dune/balanced/order_spec.rb +8 -0
- data/spec/models/dune/balanced/payout_spec.rb +104 -0
- data/spec/models/dune/balanced/refund_spec.rb +157 -0
- data/spec/observers/dune/balanced/events_observer/debit_canceled_spec.rb +32 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/fixtures_test_helper.rb +11 -0
- metadata +223 -0
@@ -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
|
data/bin/rails
ADDED
@@ -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,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'
|
data/config/routes.rb
ADDED
@@ -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,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,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: {}
|