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