balanced-ach 0.1

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 (77) hide show
  1. data/.idea/.name +1 -0
  2. data/.idea/.rakeTasks +7 -0
  3. data/.idea/balanced-ach.iml +9 -0
  4. data/.idea/encodings.xml +5 -0
  5. data/.idea/misc.xml +5 -0
  6. data/.idea/modules.xml +9 -0
  7. data/.idea/scopes/scope_settings.xml +5 -0
  8. data/.idea/vcs.xml +7 -0
  9. data/.idea/workspace.xml +561 -0
  10. data/CONTRIBUTORS +1 -0
  11. data/Gemfile +19 -0
  12. data/Gemfile.lock +64 -0
  13. data/Guardfile +7 -0
  14. data/LICENSE +22 -0
  15. data/README.md +64 -0
  16. data/Rakefile +12 -0
  17. data/balanced.gemspec +22 -0
  18. data/lib/balanced.rb +84 -0
  19. data/lib/balanced/client.rb +82 -0
  20. data/lib/balanced/error.rb +85 -0
  21. data/lib/balanced/pager.rb +201 -0
  22. data/lib/balanced/resources.rb +5 -0
  23. data/lib/balanced/resources/bank_account.rb +36 -0
  24. data/lib/balanced/resources/credit.rb +20 -0
  25. data/lib/balanced/resources/debit.rb +27 -0
  26. data/lib/balanced/resources/resource.rb +158 -0
  27. data/lib/balanced/response/balanced_exception_middleware.rb +33 -0
  28. data/lib/balanced/utils.rb +62 -0
  29. data/lib/balanced/version.rb +3 -0
  30. data/spec/balanced/pager_spec.rb +42 -0
  31. data/spec/balanced/resources/account_spec.rb +571 -0
  32. data/spec/balanced/resources/api_key_spec.rb +55 -0
  33. data/spec/balanced/resources/hold_spec.rb +55 -0
  34. data/spec/balanced/resources/marketplace_spec.rb +97 -0
  35. data/spec/balanced/resources/transactions_spec.rb +72 -0
  36. data/spec/balanced/response/balanced_exception_middleware_spec.rb +47 -0
  37. data/spec/balanced_spec.rb +77 -0
  38. data/spec/cassettes/Balanced/configure.yml +48 -0
  39. data/spec/cassettes/Balanced/configure/reconfigure_with_new_api_key.yml +48 -0
  40. data/spec/cassettes/Balanced_Account.yml +110 -0
  41. data/spec/cassettes/Balanced_Account/Account_uri/when_ApiKey_is_configured.yml +240 -0
  42. data/spec/cassettes/Balanced_Account/Account_uri/when_ApiKey_is_not_configured.yml +44 -0
  43. data/spec/cassettes/Balanced_Account/_find.yml +400 -0
  44. data/spec/cassettes/Balanced_Account/_find/_all_some_field_op_.yml +186 -0
  45. data/spec/cassettes/Balanced_Account/_find/_first_some_field_op_.yml +186 -0
  46. data/spec/cassettes/Balanced_Account/_find_by_email.yml +400 -0
  47. data/spec/cassettes/Balanced_Account/_find_by_email/email_address_does_not_exist.yml +175 -0
  48. data/spec/cassettes/Balanced_Account/_find_by_email/email_address_is_in_system.yml +186 -0
  49. data/spec/cassettes/Balanced_Account/buyer/_add_card/after_executing.yml +530 -0
  50. data/spec/cassettes/Balanced_Account/buyer/_add_card/when_executing.yml +455 -0
  51. data/spec/cassettes/Balanced_Account/buyer/_debit.yml +363 -0
  52. data/spec/cassettes/Balanced_Account/buyer/_promote_to_merchant/after_executing.yml +281 -0
  53. data/spec/cassettes/Balanced_Account/buyer/_promote_to_merchant/when_executing.yml +281 -0
  54. data/spec/cassettes/Balanced_Account/buyer/_save/after_save/attributes.yml +1013 -0
  55. data/spec/cassettes/Balanced_Account/buyer/_save/when_creating.yml +229 -0
  56. data/spec/cassettes/Balanced_Account/merchant/_add_bank_account/after_executing.yml +536 -0
  57. data/spec/cassettes/Balanced_Account/merchant/_add_bank_account/when_executing.yml +460 -0
  58. data/spec/cassettes/Balanced_Account/merchant/_save/after_save/attributes.yml +232 -0
  59. data/spec/cassettes/Balanced_Account/merchant/_save/when_creating.yml +232 -0
  60. data/spec/cassettes/Balanced_Account/merchant/new.yml +179 -0
  61. data/spec/cassettes/Balanced_ApiKey/attributes.yml +48 -0
  62. data/spec/cassettes/Balanced_ApiKey/new_key/after_configure.yml +93 -0
  63. data/spec/cassettes/Balanced_ApiKey/new_key/before_configure.yml +48 -0
  64. data/spec/cassettes/Balanced_Hold.yml +335 -0
  65. data/spec/cassettes/Balanced_Hold/_void.yml +62 -0
  66. data/spec/cassettes/Balanced_Hold/_void/after.yml +62 -0
  67. data/spec/cassettes/Balanced_Hold/_void/when_exception_is_thrown.yml +306 -0
  68. data/spec/cassettes/Balanced_Marketplace.yml +339 -0
  69. data/spec/cassettes/Balanced_Transaction.yml +1261 -0
  70. data/spec/cassettes/Balanced_Transaction/Transaction.yml +634 -0
  71. data/spec/cassettes/Balanced_Transaction/Transaction_paginate.yml +634 -0
  72. data/spec/client_spec.rb +12 -0
  73. data/spec/spec_helper.rb +31 -0
  74. data/spec/utils_spec.rb +8 -0
  75. data/upload_docs.rb +39 -0
  76. data/x.rb +22 -0
  77. metadata +199 -0
@@ -0,0 +1,201 @@
1
+ require "cgi"
2
+
3
+ module Balanced
4
+ class Pager
5
+ DEFAULT_SEP = /[&;] */n
6
+ DEFAULT_LIMIT = 10
7
+
8
+ include Enumerable
9
+
10
+ # A pager for paginating through resource records.
11
+ def initialize uri, options = {}
12
+ @uri = uri
13
+ @options = options
14
+ @page = nil
15
+ @resource_class = nil
16
+ end
17
+
18
+ def resource_class
19
+ return @resource_class unless @resource_class.nil?
20
+ load! unless @page
21
+ @resource_class = Balanced.from_uri items.first[:uri]
22
+ end
23
+
24
+ def first
25
+ load! unless @page
26
+ items.first.nil? ? nil : resource_class.construct_from_response(items.first)
27
+ end
28
+
29
+ def total
30
+ load! unless @page
31
+ @page[:total]
32
+ end
33
+
34
+ def limit
35
+ load! unless @page
36
+ @page[:limit]
37
+ end
38
+ alias limit_value limit
39
+
40
+ def offset
41
+ load! unless @page
42
+ @page[:offset]
43
+ end
44
+ alias offset_value offset
45
+
46
+ def items
47
+ load! unless @page
48
+ @page[:items]
49
+ end
50
+
51
+ def current_page
52
+ (offset / limit) + 1
53
+ end
54
+
55
+ def num_pages
56
+ num = total / limit
57
+ num += 1 if total % limit > 0
58
+ num
59
+ end
60
+
61
+ # @return [Array] Iterates through the current page of records.
62
+ # @yield [record]
63
+ def each
64
+ return enum_for :each unless block_given?
65
+
66
+ load! unless @page
67
+ loop do
68
+ @page[:items].each do |r|
69
+ yield resource_class.construct_from_response r
70
+ end
71
+ raise StopIteration if @page[:next_uri].nil?
72
+ self.next
73
+ end
74
+ end
75
+
76
+ # @return [nil]
77
+ # @see Resource.find_each
78
+ # @yield [record]
79
+ def find_each
80
+ return enum_for :find_each unless block_given?
81
+ begin
82
+ each { |record| yield record }
83
+ end while self.next
84
+ end
85
+
86
+ # @return [Array, nil] Refreshes the pager's collection of records with
87
+ # the next page.
88
+ def next
89
+ load! unless @page
90
+ next_uri = @page[:next_uri]
91
+ load_from next_uri, nil unless next_uri.nil?
92
+ end
93
+
94
+ # @return [Array, nil] Refreshes the pager's collection of records with
95
+ # the previous page.
96
+ def prev
97
+ load! unless @page
98
+ prev_uri = @page[:prev_uri]
99
+ load_from prev_uri, nil unless prev_uri.nil?
100
+ end
101
+
102
+ # @return [Array, nil] Refreshes the pager's collection of records with
103
+ # the first page.
104
+ def start
105
+ load! unless @page
106
+ first_page = @page[:first_page]
107
+ load_from first_page, nil unless first_page.nil?
108
+ end
109
+
110
+ # @return [Array, nil] Load (or reload) the pager's collection from the
111
+ # original, supplied options.
112
+ def load!
113
+ load_from @uri, @options
114
+ end
115
+ alias reload load!
116
+
117
+ # @return [Pager] Duplicates the pager, updating it with the options
118
+ # supplied. Useful for resource scopes.
119
+ # @see #initialize
120
+ def paginate options = {}
121
+ dup.instance_eval {
122
+ @page = nil
123
+ @options.update options and self
124
+ }
125
+ end
126
+ alias scoped paginate
127
+ alias where paginate
128
+
129
+ def all options = {}
130
+ paginate(options).to_a
131
+ end
132
+
133
+ def find uri
134
+ if resource_class.respond_to? :find
135
+ raise NoMethodError,
136
+ "#find must be called on #{resource_class} directly"
137
+ end
138
+
139
+ resource_class.find uri
140
+ end
141
+
142
+
143
+ private
144
+
145
+ def load_from uri, params
146
+ parsed_uri = URI.parse(uri)
147
+
148
+ params ||= {}
149
+ params = adjust_pagination_params(params)
150
+
151
+ unless parsed_uri.query.nil?
152
+ # The reason we don't use CGI::parse here is because
153
+ # the balanced api currently can't handle variable[]=value.
154
+ # Faraday ends up encoding a simple query string like:
155
+ # {"limit"=>["10"], "offset"=>["0"]}
156
+ # to limit[]=10&offset[]=0 and that's cool, but
157
+ # we have to make sure balanced supports it.
158
+ query_params = parse_query(parsed_uri.query)
159
+ params.merge! query_params
160
+ parsed_uri.query = nil
161
+ end
162
+
163
+ response = Balanced.get parsed_uri.to_s, params
164
+ @page = Balanced::Utils.hash_with_indifferent_read_access response.body
165
+ @uri = @page[:uri]
166
+ end
167
+
168
+ def adjust_pagination_params(original)
169
+ adjusted = original.dup
170
+ per = adjusted.delete(:per)
171
+ adjusted[:limit] = per unless per.nil?
172
+ page = adjusted.delete(:page)
173
+ adjusted[:offset] = (adjusted[:limit] || DEFAULT_LIMIT) * ([page, 1].max - 1) unless page.nil?
174
+ adjusted
175
+ end
176
+
177
+ # Stolen from Mongrel, with some small modifications:
178
+ # Parses a query string by breaking it up at the '&'
179
+ # and ';' characters. You can also use this to parse
180
+ # cookies by changing the characters used in the second
181
+ # parameter (which defaults to '&;').
182
+ def parse_query(qs, d = nil)
183
+ params = {}
184
+
185
+ (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
186
+ k, v = p.split('=', 2).map { |x| CGI::unescape(x) }
187
+ if (cur = params[k])
188
+ if cur.class == Array
189
+ params[k] << v
190
+ else
191
+ params[k] = [cur, v]
192
+ end
193
+ else
194
+ params[k] = v
195
+ end
196
+ end
197
+
198
+ params
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,5 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "balanced", "resources")
2
+ require "resource"
3
+ require "bank_account"
4
+ require "credit"
5
+ require "debit"
@@ -0,0 +1,36 @@
1
+ module Balanced
2
+ class BankAccount
3
+ include Balanced::Resource
4
+
5
+ def initialize attributes = {}
6
+ Balanced::Utils.stringify_keys! attributes
7
+ unless attributes.has_key? 'uri'
8
+ attributes['uri'] = self.class.uri
9
+ end
10
+ super attributes
11
+ end
12
+
13
+ def debit params
14
+ amount = params.fetch(:amount) {nil}
15
+ if amount.nil?
16
+ raise "amount is required"
17
+ end
18
+ response = Balanced.post(self.debits_uri, :amount => amount)
19
+ self.class.construct_from_response response.body
20
+ end
21
+
22
+ def credit params
23
+ amount = params.fetch(:amount) {nil}
24
+ if amount.nil?
25
+ raise "amount is required"
26
+ end
27
+ response = Balanced.post(self.credits_uri, :amount => amount)
28
+ self.class.construct_from_response response.body
29
+ end
30
+
31
+ def unstore
32
+ Balanced.delete(self.uri)
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ module Balanced
2
+ # A Credit represents a transfer of funds from your Marketplace's
3
+ # escrow account to a Merchant's Account within your Marketplace.
4
+ #
5
+ # By default, a Credit is sent to the most recently added funding
6
+ # destination associated with an Account. You may specify a specific
7
+ # funding source.
8
+ #
9
+ class Credit
10
+ include Balanced::Resource
11
+ def initialize attributes = {}
12
+ Balanced::Utils.stringify_keys! attributes
13
+ unless attributes.has_key? 'uri'
14
+ attributes['uri'] = self.class.uri
15
+ end
16
+ super attributes
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,27 @@
1
+ module Balanced
2
+ # A Debit represents a transfer of funds from a buyer's Account to your
3
+ # Marketplace's escrow account.
4
+ #
5
+ # A Debit may be created directly, or it will be created as a side-effect
6
+ # of capturing a Hold. If you create a Debit directly it will implicitly
7
+ # create the associated Hold if the funding source supports this.
8
+ #
9
+ # If no Hold is specified the Debit will by default be created using the
10
+ # most recently added funding source associated with the Account. You
11
+ # cannot change the funding source between creating a Hold and capturing
12
+ # it.
13
+ #
14
+ class Debit
15
+ include Balanced::Resource
16
+
17
+ def initialize attributes = {}
18
+ Balanced::Utils.stringify_keys! attributes
19
+ unless attributes.has_key? 'uri'
20
+ attributes['uri'] = self.class.uri
21
+ end
22
+ super attributes
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -0,0 +1,158 @@
1
+ require_relative "../pager"
2
+
3
+
4
+ module Balanced
5
+ module Resource
6
+ attr_accessor :attributes
7
+
8
+ def initialize attributes = {}
9
+ @attributes = attributes
10
+ end
11
+
12
+ # delegate the query to the pager module
13
+
14
+ def find *arguments
15
+ self.class.find *arguments
16
+ end
17
+
18
+ def save
19
+ uri = @attributes.delete('uri')
20
+ method = :post
21
+ if uri.nil?
22
+ uri = self.class.collection_path
23
+ elsif !Balanced.is_collection(uri)
24
+ method = :put
25
+ end
26
+ @response = Balanced.send(method, uri, self.attributes)
27
+ reload @response
28
+ end
29
+
30
+ def response
31
+ @response
32
+ end
33
+ private :response
34
+
35
+ def destroy
36
+ Balanced.delete @attributes[:uri]
37
+ end
38
+
39
+ def reload the_response = nil
40
+ if the_response
41
+ return if the_response.body.to_s.length.zero?
42
+ fresh = self.class.construct_from_response the_response.body
43
+ else
44
+ fresh = self.find(@attributes[:uri])
45
+ end
46
+ fresh and copy_from fresh
47
+ self
48
+ end
49
+
50
+ def copy_from other
51
+ other.instance_variables.each do |ivar|
52
+ instance_variable_set ivar, other.instance_variable_get(ivar)
53
+ end
54
+ end
55
+
56
+ def method_missing(method, *args, &block)
57
+ case method
58
+ when /(.+)\=$/
59
+ attr = method.to_s.chop
60
+ @attributes[attr] = args[0]
61
+ else
62
+ super
63
+ end
64
+ end
65
+ def self.included(base)
66
+ base.extend ClassMethods
67
+ end
68
+
69
+ module ClassMethods
70
+ def resource_name
71
+ Utils.demodulize name
72
+ end
73
+
74
+ def collection_name
75
+ Utils.pluralize Utils.underscore(resource_name)
76
+ end
77
+
78
+ def collection_path
79
+ ["/#{Balanced.config[:version]}", collection_name].compact.join '/'
80
+ end
81
+
82
+ def member_name
83
+ Utils.underscore resource_name
84
+ end
85
+
86
+ def uri
87
+ collection_path
88
+ end
89
+
90
+ def construct_from_response payload
91
+ payload = Balanced::Utils.hash_with_indifferent_read_access payload
92
+ return payload if payload[:uri].nil?
93
+ klass = Balanced.from_uri(payload[:uri])
94
+ instance = klass.new payload
95
+ payload.each do |name, value|
96
+ klass.class_eval {
97
+ attr_accessor name.to_s
98
+ }
99
+ # here is where our interpretations will begin.
100
+ # if the value is a sub-resource, lets instantiate the class
101
+ # and set it correctly
102
+ if value.instance_of? Hash and value.has_key? 'uri'
103
+ value = construct_from_response value
104
+ elsif name =~ /_uri$/
105
+ modified_name = name.sub(/_uri$/, '')
106
+ klass.instance_eval {
107
+ define_method(modified_name) {
108
+ values_class = Balanced.from_uri(value)
109
+ # if uri is a collection -> this would definitely be if it ends in a symbol
110
+ # then we should allow a lazy executor of the query pager
111
+ if Balanced.is_collection(value)
112
+ pager = Pager.new value, {}
113
+ pager.to_a
114
+ else
115
+ values_class.find(value)
116
+ end
117
+ }
118
+ }
119
+ end
120
+
121
+ instance.class.instance_eval {
122
+ define_method(name) { @attributes[name] } # Get.
123
+ define_method("#{name}=") { |value| @attributes[name] = value } # Set.
124
+ define_method("#{name}?") { !!@attributes[name].nil? } # Present.
125
+ }
126
+ instance.send("#{name}=".to_s, value)
127
+ end
128
+ instance
129
+ end
130
+
131
+ def find *arguments
132
+ scope = arguments.slice!(0)
133
+ options = arguments.slice!(0) || {}
134
+ case scope
135
+ when :all then all(options)
136
+ when :first then paginate(options).first
137
+ else
138
+ response = Balanced.get scope, options
139
+ construct_from_response response.body
140
+ end
141
+ end
142
+
143
+ def paginate options = {}
144
+ Pager.new uri, options
145
+ end
146
+ alias scoped paginate
147
+ alias where paginate
148
+
149
+ def all options = {}
150
+ pager = paginate(options)
151
+ pager.to_a
152
+ end
153
+
154
+ end
155
+
156
+ end
157
+ end
158
+