money_mover 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +80 -0
  6. data/lib/money_mover/dwolla/account_client.rb +9 -0
  7. data/lib/money_mover/dwolla/account_token.rb +24 -0
  8. data/lib/money_mover/dwolla/api_connection.rb +18 -0
  9. data/lib/money_mover/dwolla/api_server_response.rb +31 -0
  10. data/lib/money_mover/dwolla/application_client.rb +9 -0
  11. data/lib/money_mover/dwolla/application_token.rb +25 -0
  12. data/lib/money_mover/dwolla/client.rb +24 -0
  13. data/lib/money_mover/dwolla/config.rb +33 -0
  14. data/lib/money_mover/dwolla/environment_urls.rb +35 -0
  15. data/lib/money_mover/dwolla/error_handler.rb +30 -0
  16. data/lib/money_mover/dwolla/models/account_funding_source.rb +28 -0
  17. data/lib/money_mover/dwolla/models/api_resource.rb +70 -0
  18. data/lib/money_mover/dwolla/models/customer.rb +53 -0
  19. data/lib/money_mover/dwolla/models/document.rb +22 -0
  20. data/lib/money_mover/dwolla/models/funding_source.rb +30 -0
  21. data/lib/money_mover/dwolla/models/micro_deposit_initiation.rb +19 -0
  22. data/lib/money_mover/dwolla/models/micro_deposit_verification.rb +31 -0
  23. data/lib/money_mover/dwolla/models/receive_only_business_customer.rb +27 -0
  24. data/lib/money_mover/dwolla/models/receive_only_customer.rb +19 -0
  25. data/lib/money_mover/dwolla/models/root_account.rb +27 -0
  26. data/lib/money_mover/dwolla/models/transfer.rb +33 -0
  27. data/lib/money_mover/dwolla/models/unverified_business_customer.rb +43 -0
  28. data/lib/money_mover/dwolla/models/unverified_customer.rb +27 -0
  29. data/lib/money_mover/dwolla/models/verified_business_customer.rb +53 -0
  30. data/lib/money_mover/dwolla/models/webhook_subscription.rb +36 -0
  31. data/lib/money_mover/dwolla/token.rb +26 -0
  32. data/lib/money_mover/dwolla.rb +44 -0
  33. data/lib/money_mover/standalone_errors.rb +12 -0
  34. data/lib/money_mover/version.rb +3 -0
  35. data/lib/money_mover.rb +15 -0
  36. data/money_mover.gemspec +27 -0
  37. data/spec/integration/money_mover/dwolla/customers/create_spec.rb +75 -0
  38. data/spec/integration/money_mover/dwolla/customers/find_spec.rb +71 -0
  39. data/spec/integration/money_mover/dwolla/customers/funding-sources/create_spec.rb +40 -0
  40. data/spec/integration/money_mover/dwolla/documents/create_spec.rb +62 -0
  41. data/spec/integration/money_mover/dwolla/funding-sources/micro-deposits/initiation_spec.rb +52 -0
  42. data/spec/integration/money_mover/dwolla/funding-sources/micro-deposits/verification_spec.rb +77 -0
  43. data/spec/integration/money_mover/dwolla/transfers/create_spec.rb +80 -0
  44. data/spec/lib/money_mover/dwolla/client_spec.rb +101 -0
  45. data/spec/lib/money_mover/dwolla/config_spec.rb +20 -0
  46. data/spec/lib/money_mover/dwolla/error_handler_spec.rb +78 -0
  47. data/spec/lib/money_mover/dwolla/funding_source_spec.rb +94 -0
  48. data/spec/lib/money_mover/dwolla/microdeposits_initiation_spec.rb +36 -0
  49. data/spec/lib/money_mover/dwolla/root_account_spec.rb +86 -0
  50. data/spec/lib/money_mover/dwolla/token_spec.rb +101 -0
  51. data/spec/lib/money_mover/dwolla/transfer_spec.rb +94 -0
  52. data/spec/lib/money_mover/dwolla/webhook_subscription_spec.rb +199 -0
  53. data/spec/spec_helper.rb +121 -0
  54. data/spec/support/dwolla_helper.rb +330 -0
  55. data/spec/support/fixtures/sample.jpg +0 -0
  56. metadata +196 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f9f28c16cd835ccdab226255564ecb0bcaf9e5d4
4
+ data.tar.gz: 73a7dfbfa1c0603072668b09d37547a918586b30
5
+ SHA512:
6
+ metadata.gz: 5d69bd6eafd2db81ca96f2b7fc419ecd8d4ab54b48c10e949f5054432d65c42e64db0a6c8db98ea748af6768536dff3589c0c7267834dba4a47bf2646c5bd07c
7
+ data.tar.gz: f6e6a950ed63bb3a46fbd3c3f0cfef703fa7d1704eb3879908fbb873f52154c2fd4e08faf701f4454cd06692b564e39e4092bc33f1a7372a07ae6fb0fa9c39ef
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake'
7
+ gem 'rspec'
8
+ gem 'webmock'
9
+ gem 'shoulda-matchers'
10
+ gem 'activesupport'
11
+
12
+ gem 'activemodel'
13
+ gem 'faraday'
14
+ gem 'faraday_middleware'
15
+
16
+ gem "rack-test"
17
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ money_mover (0.0.1)
5
+ activemodel (~> 4.2, >= 4.2.6)
6
+ activesupport (~> 4.2, >= 4.2.6)
7
+ faraday (~> 0.9, >= 0.9.0)
8
+ faraday_middleware (~> 0.9, >= 0.9.0)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ activemodel (4.2.7)
14
+ activesupport (= 4.2.7)
15
+ builder (~> 3.1)
16
+ activesupport (4.2.7)
17
+ i18n (~> 0.7)
18
+ json (~> 1.7, >= 1.7.7)
19
+ minitest (~> 5.1)
20
+ thread_safe (~> 0.3, >= 0.3.4)
21
+ tzinfo (~> 1.1)
22
+ addressable (2.4.0)
23
+ builder (3.2.2)
24
+ crack (0.4.3)
25
+ safe_yaml (~> 1.0.0)
26
+ diff-lcs (1.2.5)
27
+ faraday (0.9.2)
28
+ multipart-post (>= 1.2, < 3)
29
+ faraday_middleware (0.10.0)
30
+ faraday (>= 0.7.4, < 0.10)
31
+ hashdiff (0.3.0)
32
+ i18n (0.7.0)
33
+ json (1.8.3)
34
+ minitest (5.9.0)
35
+ multipart-post (2.0.0)
36
+ rack (2.0.1)
37
+ rack-test (0.6.3)
38
+ rack (>= 1.0)
39
+ rake (11.2.2)
40
+ rspec (3.5.0)
41
+ rspec-core (~> 3.5.0)
42
+ rspec-expectations (~> 3.5.0)
43
+ rspec-mocks (~> 3.5.0)
44
+ rspec-core (3.5.1)
45
+ rspec-support (~> 3.5.0)
46
+ rspec-expectations (3.5.0)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.5.0)
49
+ rspec-mocks (3.5.0)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.5.0)
52
+ rspec-support (3.5.0)
53
+ safe_yaml (1.0.4)
54
+ shoulda-matchers (3.1.1)
55
+ activesupport (>= 4.0.0)
56
+ thread_safe (0.3.5)
57
+ tzinfo (1.2.2)
58
+ thread_safe (~> 0.1)
59
+ webmock (2.1.0)
60
+ addressable (>= 2.3.6)
61
+ crack (>= 0.3.2)
62
+ hashdiff
63
+
64
+ PLATFORMS
65
+ ruby
66
+
67
+ DEPENDENCIES
68
+ activemodel
69
+ activesupport
70
+ faraday
71
+ faraday_middleware
72
+ money_mover!
73
+ rack-test
74
+ rake
75
+ rspec
76
+ shoulda-matchers
77
+ webmock
78
+
79
+ BUNDLED WITH
80
+ 1.11.2
@@ -0,0 +1,9 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class AccountClient < Client
4
+ def initialize
5
+ super AccountToken.new.access_token
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class AccountToken < Token
4
+ def access_token
5
+ @ach_config.account_token_provider.access_token
6
+ end
7
+
8
+ def refresh_token
9
+ @ach_config.account_token_provider.refresh_token
10
+ end
11
+
12
+ private
13
+
14
+ def create_params
15
+ {
16
+ grant_type: :refresh_token,
17
+ client_id: @ach_config.api_key,
18
+ client_secret: @ach_config.api_secret_key,
19
+ refresh_token: refresh_token
20
+ }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ApiConnection
4
+ attr_reader :connection
5
+
6
+ def initialize(access_token, url_provider = EnvironmentUrls.new)
7
+ @connection ||= Faraday.new(url: url_provider.api_url) do |faraday|
8
+ faraday.authorization :Bearer, access_token if access_token
9
+ faraday.headers[:accept] = "application/vnd.dwolla.v1.hal+json"
10
+ faraday.request :multipart
11
+ faraday.request :json
12
+ faraday.response :json, content_type: /\bjson$/
13
+ faraday.adapter Faraday.default_adapter
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ApiServerResponse
4
+ attr_reader :response
5
+
6
+ def initialize(response)
7
+ @response = response
8
+ end
9
+
10
+ def body
11
+ HashWithIndifferentAccess.new @response.body
12
+ end
13
+
14
+ def errors
15
+ @errors ||= ErrorHandler.new(body).errors
16
+ end
17
+
18
+ def success?
19
+ @response.status >= 200 && @response.status < 400
20
+ end
21
+
22
+ def resource_location
23
+ @response.headers[:location] if @response.headers[:location]
24
+ end
25
+
26
+ def resource_id
27
+ resource_location.split('/').last rescue nil
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ApplicationClient < Client
4
+ def initialize
5
+ super ApplicationToken.new.access_token
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ApplicationToken < Token
4
+ def access_token
5
+ new_token = request_new_token!
6
+ new_token.access_token
7
+ end
8
+
9
+ ## NOTE: application does not have refresh token, hit API again to get new access token
10
+ def refresh_token
11
+ access_token
12
+ end
13
+
14
+ private
15
+
16
+ def create_params
17
+ {
18
+ grant_type: :client_credentials,
19
+ client_id: @ach_config.api_key,
20
+ client_secret: @ach_config.api_secret_key
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class Client
4
+ delegate :api_url, :token_url, :auth_url, to: :@url_provider
5
+
6
+ def initialize(access_token = nil, url_provider = EnvironmentUrls.new)
7
+ @url_provider = url_provider
8
+ @connection = ApiConnection.new(access_token).connection
9
+ end
10
+
11
+ def post(url, params)
12
+ ApiServerResponse.new @connection.post(url, params)
13
+ end
14
+
15
+ def get(url, params = nil)
16
+ ApiServerResponse.new @connection.get(url, params)
17
+ end
18
+
19
+ def delete(url, params = nil)
20
+ ApiServerResponse.new @connection.delete(url, params)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class Config
4
+ def initialize(config = MoneyMover::Dwolla.config_provider)
5
+ @config = config
6
+ end
7
+
8
+ def webhook_secret_key
9
+ @config.webhook_secret_key
10
+ end
11
+
12
+ def webhook_callback_url
13
+ @config.webhook_callback_url
14
+ end
15
+
16
+ def api_key
17
+ @config.api_key
18
+ end
19
+
20
+ def api_secret_key
21
+ @config.api_secret_key
22
+ end
23
+
24
+ def environment
25
+ @config.environment
26
+ end
27
+
28
+ def account_token_provider
29
+ @config.account_token_provider.new
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class EnvironmentUrls
4
+ ENVIRONMENTS = {
5
+ :production => {
6
+ :auth_url => "https://www.dwolla.com/oauth/v2/authenticate",
7
+ :token_url => "https://www.dwolla.com/oauth/v2/token",
8
+ :api_url => "https://api.dwolla.com"
9
+ },
10
+ :sandbox => {
11
+ :auth_url => "https://uat.dwolla.com/oauth/v2/authenticate",
12
+ :token_url => "https://uat.dwolla.com/oauth/v2/token",
13
+ :api_url => "https://api-uat.dwolla.com"
14
+ }
15
+ }
16
+
17
+ def initialize(ach_config = Config.new)
18
+ @ach_config = ach_config
19
+ @environment = @ach_config.environment
20
+ end
21
+
22
+ def api_url
23
+ ENVIRONMENTS[@environment][:api_url]
24
+ end
25
+
26
+ def token_url
27
+ ENVIRONMENTS[@environment][:token_url]
28
+ end
29
+
30
+ def auth_url
31
+ ENVIRONMENTS[@environment][:auth_url]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ErrorHandler
4
+ def initialize(server_error)
5
+ @server_error = server_error
6
+ @errors = StandaloneErrors.new
7
+ end
8
+
9
+ def errors
10
+ @errors.clear
11
+
12
+ if @server_error[:_embedded]
13
+ @server_error[:_embedded][:errors].each do |error|
14
+ if error[:path]
15
+ key = error[:path].split('/')[1].to_sym
16
+ else
17
+ key = :base
18
+ end
19
+
20
+ @errors.add key, error[:message]
21
+ end
22
+ else
23
+ @errors.add :base, @server_error[:message]
24
+ end
25
+
26
+ @errors
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class AccountFundingSource < ApiResource
4
+ attr_accessor :status, :type, :name, :created
5
+
6
+ def initialize(attrs)
7
+ super id: attrs[:id], name: attrs[:name], status: attrs[:status], type: attrs[:type], created: attrs[:created], _links: attrs[:_links], _embedded: attrs[:_embedded]
8
+ end
9
+
10
+ def self.all(account_id)
11
+ client = AccountClient.new
12
+
13
+ response = client.get fetch_endpoint(account_id)
14
+ response.body[:_embedded][:"funding-sources"].map {|source| new(source) }
15
+ end
16
+
17
+ def bank_account?
18
+ type == 'bank'
19
+ end
20
+
21
+ private
22
+
23
+ def self.fetch_endpoint(account_id)
24
+ "/accounts/#{account_id}/funding-sources"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,70 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ApiResource
4
+ include ActiveModel::Model
5
+
6
+ attr_accessor :id, :_links, :_embedded
7
+
8
+ attr_reader :resource_location
9
+
10
+ def initialize(attrs = {}, client = AccountClient.new)
11
+ @id = attrs[:id]
12
+ @resource_location = attrs[:resource_location]
13
+ @_links = attrs[:_links]
14
+ @client = client
15
+
16
+ super attrs
17
+ end
18
+
19
+ def self.fetch
20
+ client = AccountClient.new
21
+
22
+ response = client.get fetch_endpoint
23
+
24
+ if response.success?
25
+ new response.body
26
+ else
27
+ #raise 'Customer Not Found'
28
+ #puts "error: #{response.body}"
29
+ end
30
+ end
31
+
32
+ def save
33
+ return false unless valid?
34
+
35
+ response = @client.post create_endpoint, create_params
36
+
37
+ if response.success?
38
+ @resource_location = response.resource_location
39
+ @id = response.resource_id
40
+ else
41
+ populate_errors_from_response(response)
42
+ end
43
+
44
+ errors.empty?
45
+ end
46
+
47
+ def destroy
48
+ response = @client.delete resource_endpoint
49
+
50
+ populate_errors_from_response(response) unless response.success?
51
+
52
+ errors.empty?
53
+ end
54
+
55
+ private
56
+
57
+ def populate_errors_from_response(response)
58
+ response.errors.each {|key, message| errors.add key, message }
59
+ end
60
+
61
+ def resource_endpoint
62
+ "#{create_endpoint}/#{id}"
63
+ end
64
+
65
+ def create_params
66
+ {}
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,53 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class Customer < ApiResource
4
+
5
+ attr_accessor :firstName,
6
+ :lastName,
7
+ :email,
8
+ :address1,
9
+ :address2,
10
+ :city,
11
+ :state,
12
+ :postalCode,
13
+ :dateOfBirth,
14
+ :ssn,
15
+ :phone,
16
+ :businessClassification,
17
+ :businessType,
18
+ :businessName,
19
+ :ein,
20
+ :doingBusinessAs,
21
+ :website,
22
+ :type,
23
+ :ipAddress,
24
+ :status,
25
+ :created
26
+
27
+ validates_presence_of :type
28
+
29
+ def self.find(id)
30
+ client = AccountClient.new
31
+
32
+ response = client.get fetch_endpoint(id)
33
+
34
+ if response.success?
35
+ new response.body
36
+ else
37
+ raise 'Customer Not Found'
38
+ #puts "error: #{response.body}"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def self.fetch_endpoint(id)
45
+ "/customers/#{id}"
46
+ end
47
+
48
+ def create_endpoint
49
+ "/customers"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class Document < ApiResource
4
+ attr_accessor :customer_id, :file, :documentType
5
+
6
+ validates_presence_of :customer_id, :file, :documentType
7
+
8
+ private
9
+
10
+ def create_endpoint
11
+ "/customers/#{customer_id}/documents"
12
+ end
13
+
14
+ def create_params
15
+ {
16
+ documentType: documentType,
17
+ file: Faraday::UploadIO.new(file, file.content_type)
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class FundingSource < ApiResource
4
+ attr_accessor :name, :type, :routingNumber, :accountNumber
5
+
6
+ validates_presence_of :name, :type, :routingNumber, :accountNumber
7
+ validates_inclusion_of :type, :in => %w( checking savings )
8
+
9
+ def initialize(customer_id, attrs={})
10
+ @customer_id = customer_id
11
+ super attrs
12
+ end
13
+
14
+ private
15
+
16
+ def create_endpoint
17
+ "/customers/#{@customer_id}/funding-sources"
18
+ end
19
+
20
+ def create_params
21
+ {
22
+ name: name,
23
+ type: type,
24
+ routingNumber: routingNumber,
25
+ accountNumber: accountNumber
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class MicroDepositInitiation < ApiResource
4
+ attr_accessor :funding_source_id
5
+
6
+ validates_presence_of :funding_source_id
7
+
8
+ private
9
+
10
+ def create_endpoint
11
+ "/funding-sources/#{funding_source_id}/micro-deposits"
12
+ end
13
+
14
+ def create_params
15
+ {}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class MicroDepositVerification < ApiResource
4
+ attr_accessor :funding_source_id, :amount1, :amount2
5
+
6
+ validates_presence_of :funding_source_id, :amount1, :amount2
7
+
8
+ validates_numericality_of :amount1, less_than_or_equal_to: 0.10, greater_than: 0
9
+ validates_numericality_of :amount2, less_than_or_equal_to: 0.10, greater_than: 0
10
+
11
+ private
12
+
13
+ def create_endpoint
14
+ "/funding-sources/#{funding_source_id}/micro-deposits"
15
+ end
16
+
17
+ def create_params
18
+ {
19
+ amount1: {
20
+ value: amount1,
21
+ currency: "USD"
22
+ },
23
+ amount2: {
24
+ value: amount2,
25
+ currency: "USD"
26
+ }
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ReceiveOnlyBusinessCustomer < Customer
4
+ def initialize(attrs = {})
5
+ attrs[:type] = 'receive-only'
6
+ super attrs
7
+ end
8
+ private
9
+
10
+ validates_presence_of :firstName, :lastName, :email
11
+
12
+ def create_params
13
+ create_attrs = {
14
+ firstName: firstName,
15
+ lastName: lastName,
16
+ email: email,
17
+ }
18
+
19
+ create_attrs[:businessName] = businessName if businessName.present?
20
+ create_attrs[:ipAddress] = ipAddress if ipAddress.present?
21
+ create_attrs[:type] = type
22
+
23
+ create_attrs
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module MoneyMover
2
+ module Dwolla
3
+ class ReceiveOnlyCustomer < Customer
4
+ private
5
+
6
+ validates_presence_of :firstName, :lastName, :email
7
+
8
+ def create_params
9
+ {
10
+ firstName: firstName,
11
+ lastName: lastName,
12
+ email: email,
13
+ type: 'receive-only',
14
+ ipAddress: ipAddress
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end