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.
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/balanced-ach.iml +9 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +561 -0
- data/CONTRIBUTORS +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +64 -0
- data/Guardfile +7 -0
- data/LICENSE +22 -0
- data/README.md +64 -0
- data/Rakefile +12 -0
- data/balanced.gemspec +22 -0
- data/lib/balanced.rb +84 -0
- data/lib/balanced/client.rb +82 -0
- data/lib/balanced/error.rb +85 -0
- data/lib/balanced/pager.rb +201 -0
- data/lib/balanced/resources.rb +5 -0
- data/lib/balanced/resources/bank_account.rb +36 -0
- data/lib/balanced/resources/credit.rb +20 -0
- data/lib/balanced/resources/debit.rb +27 -0
- data/lib/balanced/resources/resource.rb +158 -0
- data/lib/balanced/response/balanced_exception_middleware.rb +33 -0
- data/lib/balanced/utils.rb +62 -0
- data/lib/balanced/version.rb +3 -0
- data/spec/balanced/pager_spec.rb +42 -0
- data/spec/balanced/resources/account_spec.rb +571 -0
- data/spec/balanced/resources/api_key_spec.rb +55 -0
- data/spec/balanced/resources/hold_spec.rb +55 -0
- data/spec/balanced/resources/marketplace_spec.rb +97 -0
- data/spec/balanced/resources/transactions_spec.rb +72 -0
- data/spec/balanced/response/balanced_exception_middleware_spec.rb +47 -0
- data/spec/balanced_spec.rb +77 -0
- data/spec/cassettes/Balanced/configure.yml +48 -0
- data/spec/cassettes/Balanced/configure/reconfigure_with_new_api_key.yml +48 -0
- data/spec/cassettes/Balanced_Account.yml +110 -0
- data/spec/cassettes/Balanced_Account/Account_uri/when_ApiKey_is_configured.yml +240 -0
- data/spec/cassettes/Balanced_Account/Account_uri/when_ApiKey_is_not_configured.yml +44 -0
- data/spec/cassettes/Balanced_Account/_find.yml +400 -0
- data/spec/cassettes/Balanced_Account/_find/_all_some_field_op_.yml +186 -0
- data/spec/cassettes/Balanced_Account/_find/_first_some_field_op_.yml +186 -0
- data/spec/cassettes/Balanced_Account/_find_by_email.yml +400 -0
- data/spec/cassettes/Balanced_Account/_find_by_email/email_address_does_not_exist.yml +175 -0
- data/spec/cassettes/Balanced_Account/_find_by_email/email_address_is_in_system.yml +186 -0
- data/spec/cassettes/Balanced_Account/buyer/_add_card/after_executing.yml +530 -0
- data/spec/cassettes/Balanced_Account/buyer/_add_card/when_executing.yml +455 -0
- data/spec/cassettes/Balanced_Account/buyer/_debit.yml +363 -0
- data/spec/cassettes/Balanced_Account/buyer/_promote_to_merchant/after_executing.yml +281 -0
- data/spec/cassettes/Balanced_Account/buyer/_promote_to_merchant/when_executing.yml +281 -0
- data/spec/cassettes/Balanced_Account/buyer/_save/after_save/attributes.yml +1013 -0
- data/spec/cassettes/Balanced_Account/buyer/_save/when_creating.yml +229 -0
- data/spec/cassettes/Balanced_Account/merchant/_add_bank_account/after_executing.yml +536 -0
- data/spec/cassettes/Balanced_Account/merchant/_add_bank_account/when_executing.yml +460 -0
- data/spec/cassettes/Balanced_Account/merchant/_save/after_save/attributes.yml +232 -0
- data/spec/cassettes/Balanced_Account/merchant/_save/when_creating.yml +232 -0
- data/spec/cassettes/Balanced_Account/merchant/new.yml +179 -0
- data/spec/cassettes/Balanced_ApiKey/attributes.yml +48 -0
- data/spec/cassettes/Balanced_ApiKey/new_key/after_configure.yml +93 -0
- data/spec/cassettes/Balanced_ApiKey/new_key/before_configure.yml +48 -0
- data/spec/cassettes/Balanced_Hold.yml +335 -0
- data/spec/cassettes/Balanced_Hold/_void.yml +62 -0
- data/spec/cassettes/Balanced_Hold/_void/after.yml +62 -0
- data/spec/cassettes/Balanced_Hold/_void/when_exception_is_thrown.yml +306 -0
- data/spec/cassettes/Balanced_Marketplace.yml +339 -0
- data/spec/cassettes/Balanced_Transaction.yml +1261 -0
- data/spec/cassettes/Balanced_Transaction/Transaction.yml +634 -0
- data/spec/cassettes/Balanced_Transaction/Transaction_paginate.yml +634 -0
- data/spec/client_spec.rb +12 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/utils_spec.rb +8 -0
- data/upload_docs.rb +39 -0
- data/x.rb +22 -0
- 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,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
|
+
|