money_mover 0.0.3

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 (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