balanced-ach 0.1

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