paid 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +5 -1
- data/.travis.yml +16 -0
- data/History.txt +4 -0
- data/README.md +58 -0
- data/Rakefile +9 -29
- data/VERSION +1 -0
- data/bin/paid-console +7 -0
- data/gemfiles/default-with-activesupport.gemfile +10 -0
- data/gemfiles/json.gemfile +12 -0
- data/gemfiles/yajl.gemfile +12 -0
- data/lib/paid.rb +129 -177
- data/lib/paid/account.rb +14 -1
- data/lib/paid/api_class.rb +336 -0
- data/lib/paid/api_list.rb +47 -0
- data/lib/paid/api_resource.rb +8 -25
- data/lib/paid/api_singleton.rb +5 -0
- data/lib/paid/customer.rb +36 -21
- data/lib/paid/errors/api_error.rb +6 -0
- data/lib/paid/event.rb +22 -1
- data/lib/paid/invoice.rb +16 -21
- data/lib/paid/plan.rb +18 -2
- data/lib/paid/subscription.rb +17 -11
- data/lib/paid/transaction.rb +19 -12
- data/lib/paid/util.rb +53 -106
- data/lib/paid/version.rb +1 -1
- data/paid.gemspec +10 -11
- data/tasks/api_test.rb +187 -0
- data/test/mock_resource.rb +69 -0
- data/test/paid/account_test.rb +41 -4
- data/test/paid/api_class_test.rb +412 -0
- data/test/paid/api_list_test.rb +17 -0
- data/test/paid/api_resource_test.rb +13 -343
- data/test/paid/api_singleton_test.rb +12 -0
- data/test/paid/authentication_test.rb +50 -0
- data/test/paid/customer_test.rb +189 -29
- data/test/paid/event_test.rb +74 -0
- data/test/paid/invoice_test.rb +101 -20
- data/test/paid/plan_test.rb +84 -8
- data/test/paid/status_codes_test.rb +63 -0
- data/test/paid/subscription_test.rb +100 -20
- data/test/paid/transaction_test.rb +110 -37
- data/test/paid/util_test.rb +15 -24
- data/test/test_data.rb +144 -93
- data/test/test_helper.rb +6 -4
- metadata +32 -26
- data/Gemfile.lock +0 -54
- data/README.rdoc +0 -35
- data/lib/data/ca-certificates.crt +0 -0
- data/lib/paid/alias.rb +0 -16
- data/lib/paid/api_operations/create.rb +0 -17
- data/lib/paid/api_operations/delete.rb +0 -11
- data/lib/paid/api_operations/list.rb +0 -17
- data/lib/paid/api_operations/update.rb +0 -57
- data/lib/paid/certificate_blacklist.rb +0 -55
- data/lib/paid/list_object.rb +0 -37
- data/lib/paid/paid_object.rb +0 -187
- data/lib/paid/singleton_api_resource.rb +0 -20
- data/lib/tasks/paid_tasks.rake +0 -4
- data/test/paid/alias_test.rb +0 -22
- data/test/paid/certificate_blacklist_test.rb +0 -18
- data/test/paid/list_object_test.rb +0 -16
- data/test/paid/paid_object_test.rb +0 -27
- data/test/paid/properties_test.rb +0 -103
data/lib/paid/account.rb
CHANGED
@@ -1,4 +1,17 @@
|
|
1
1
|
module Paid
|
2
|
-
class Account <
|
2
|
+
class Account < APISingleton
|
3
|
+
# attribute :object inherited from APISingleton
|
4
|
+
attribute :id
|
5
|
+
attribute :business_name
|
6
|
+
attribute :business_url
|
7
|
+
attribute :business_logo
|
8
|
+
|
9
|
+
api_class_method :retrieve, :get, ":path"
|
10
|
+
|
11
|
+
def self.path
|
12
|
+
"/v0/account"
|
13
|
+
end
|
14
|
+
|
15
|
+
APIClass.register_subclass(self, "account")
|
3
16
|
end
|
4
17
|
end
|
@@ -0,0 +1,336 @@
|
|
1
|
+
module Paid
|
2
|
+
class APIClass
|
3
|
+
attr_accessor :json
|
4
|
+
|
5
|
+
def self.path
|
6
|
+
raise NotImplementedError.new("APIClass is an abstract class. Please refer to its subclasses: #{subclasses}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def path
|
10
|
+
raise NotImplementedError.new("APIClass is an abstract class. Please refer to its subclasses: #{APIClass.subclasses}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.api_class_method(name, method, path=nil, opts={})
|
14
|
+
singleton = class << self; self end
|
15
|
+
singleton.send(:define_method, name, api_lambda(name, method, path, opts))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.api_instance_method(name, method, path=nil, opts={})
|
19
|
+
self.send(:define_method, name, api_lambda(name, method, path, opts))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.attribute(name, klass=nil)
|
23
|
+
@attribute_names ||= Set.new
|
24
|
+
@attribute_names << name.to_sym
|
25
|
+
|
26
|
+
self.send(:define_method, "#{name}", attribute_get_lambda(name))
|
27
|
+
self.send(:define_method, "#{name}=", attribute_set_lambda(name, klass))
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes
|
31
|
+
attributes = {}
|
32
|
+
self.class.attribute_names.each do |attr|
|
33
|
+
attributes[attr.to_sym] = self.send(attr)
|
34
|
+
end
|
35
|
+
attributes
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_attributes
|
39
|
+
attributes.select{|k, v| !v.nil? }
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.attribute_names
|
43
|
+
@attribute_names ||= Set.new
|
44
|
+
unless self == APIClass
|
45
|
+
@attribute_names + self.superclass.attribute_names
|
46
|
+
else
|
47
|
+
@attribute_names
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def mark_attribute_changed(attr_name)
|
52
|
+
@changed_attribute_names ||= Set.new
|
53
|
+
@changed_attribute_names << attr_name.to_sym
|
54
|
+
end
|
55
|
+
|
56
|
+
def changed_attribute_names
|
57
|
+
@changed_attribute_names ||= Set.new
|
58
|
+
attributes.each do |key, val|
|
59
|
+
next if @changed_attribute_names.include?(key)
|
60
|
+
if val.is_a?(Array) || val.is_a?(Hash)
|
61
|
+
@changed_attribute_names << key if json[key] != val
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@changed_attribute_names
|
65
|
+
end
|
66
|
+
|
67
|
+
def changed_attributes
|
68
|
+
ret = {}
|
69
|
+
changed_attribute_names.each do |attr|
|
70
|
+
ret[attr] = send(attr)
|
71
|
+
end
|
72
|
+
ret
|
73
|
+
end
|
74
|
+
|
75
|
+
def clear_changed_attributes
|
76
|
+
@changed_attribute_names = Set.new
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def self.changed_lambda
|
81
|
+
# This runs in the context of an instance since it is used in
|
82
|
+
# an api_instance_method
|
83
|
+
lambda do |instance|
|
84
|
+
instance.changed_attributes
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(id=nil)
|
89
|
+
refresh_from(id)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.construct(json={})
|
93
|
+
self.new.refresh_from(json)
|
94
|
+
end
|
95
|
+
|
96
|
+
def refresh_from(json={})
|
97
|
+
unless json.is_a?(Hash)
|
98
|
+
json = { :id => json }
|
99
|
+
end
|
100
|
+
self.json = Util.sorta_deep_clone(json)
|
101
|
+
|
102
|
+
json.each do |k, v|
|
103
|
+
if self.class.attribute_names.include?(k.to_sym)
|
104
|
+
self.send("#{k}=", v)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
clear_changed_attributes
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Alias, but dont declare it as one because we need to use overloaded methods.
|
112
|
+
def construct(json={})
|
113
|
+
refresh_from(json)
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.subclasses
|
117
|
+
return @subclasses ||= Set.new
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.subclass_fetch(name)
|
121
|
+
@subclasses_hash ||= {}
|
122
|
+
if @subclasses_hash.has_key?(name)
|
123
|
+
@subclasses_hash[name]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.register_subclass(subclass, name=nil)
|
128
|
+
@subclasses ||= Set.new
|
129
|
+
@subclasses << subclass
|
130
|
+
|
131
|
+
unless name.nil?
|
132
|
+
@subclasses_hash ||= {}
|
133
|
+
@subclasses_hash[name] = subclass
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def inspect
|
138
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
139
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(set_attributes)
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_json(*a)
|
143
|
+
JSON.generate(set_attributes)
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def instance_variables_include?(name)
|
150
|
+
if RUBY_VERSION <= '1.9'
|
151
|
+
instance_variables.include?("@#{name}")
|
152
|
+
else
|
153
|
+
instance_variables.include?("@#{name}".to_sym)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.attribute_get_lambda(name)
|
158
|
+
lambda do
|
159
|
+
unless instance_variables_include?(name)
|
160
|
+
nil
|
161
|
+
else
|
162
|
+
instance_variable_get("@#{name}")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.attribute_set_lambda(name, klass=nil)
|
168
|
+
lambda do |val|
|
169
|
+
if klass
|
170
|
+
val = klass.construct(val)
|
171
|
+
end
|
172
|
+
instance_variable_set("@#{name}", val)
|
173
|
+
mark_attribute_changed(name)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.api_lambda(out_name, out_method, out_path=nil, out_opts={})
|
178
|
+
# Path, Opts, and Klass are all optional, so we have to determine
|
179
|
+
# which were provided using the criteria:
|
180
|
+
temp = [out_path, out_opts]
|
181
|
+
out_path = temp.select{ |t| t.is_a?(String) }.first || nil
|
182
|
+
out_opts = temp.select{ |t| t.is_a?(Hash) }.first || {}
|
183
|
+
|
184
|
+
out_arg_names = out_opts[:arguments] || []
|
185
|
+
out_constructor = out_opts[:constructor] || :self
|
186
|
+
out_default_params = out_opts[:default_params] || {}
|
187
|
+
|
188
|
+
lambda do |*args|
|
189
|
+
# Make sure we have clean data
|
190
|
+
constructor = out_constructor
|
191
|
+
method = out_method
|
192
|
+
path = nil
|
193
|
+
path = out_path.dup if out_path
|
194
|
+
arg_names = nil
|
195
|
+
arg_names = out_arg_names.dup if out_arg_names
|
196
|
+
default_params = out_default_params # dont need to dup this since it isn't modified directly
|
197
|
+
|
198
|
+
validate_args(arg_names, *args)
|
199
|
+
arguments = compose_arguments(method, arg_names, *args)
|
200
|
+
composed_path = compose_api_path(path, arguments)
|
201
|
+
unused_args = determine_unused_args(path, arg_names, arguments)
|
202
|
+
arguments[:params] = compose_params(arguments[:params], unused_args, default_params)
|
203
|
+
|
204
|
+
resp = Paid.request(method, composed_path, arguments[:params], arguments[:opts])
|
205
|
+
|
206
|
+
|
207
|
+
if constructor.is_a?(Class)
|
208
|
+
constructor.construct(resp)
|
209
|
+
elsif constructor.is_a?(Proc)
|
210
|
+
constructor.call(resp)
|
211
|
+
elsif constructor.is_a?(Symbol)
|
212
|
+
if constructor == :self
|
213
|
+
self.construct(resp)
|
214
|
+
else
|
215
|
+
raise ArgumentError.new("Invalid constructor. See method definition.")
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.validate_args(arg_names, *args)
|
222
|
+
# Make sure we have valid arguments
|
223
|
+
if args.length > arg_names.length
|
224
|
+
if args.length > arg_names.length + 2 # more than params and opts were included
|
225
|
+
raise ArgumentError.new("Too many arguments")
|
226
|
+
else
|
227
|
+
# Params and opts are allowed, but they must be hashes
|
228
|
+
args[arg_names.length..-1].each do |arg|
|
229
|
+
unless arg.is_a?(Hash) || arg.nil?
|
230
|
+
raise ArgumentError.new("Invalid Param or Opts argument")
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
if args.length < arg_names.length
|
237
|
+
missing = arg_names[args.length..-1]
|
238
|
+
raise ArgumentError.new("Missing arguments #{missing}")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
def validate_args(arg_names, *args)
|
242
|
+
self.class.validate_args(arg_names, *args)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Priority: params > unused_args > default_params
|
246
|
+
def self.compose_params(params={}, unused_args={}, default_params={}, this=self)
|
247
|
+
ret = {}
|
248
|
+
|
249
|
+
# Handle the default params
|
250
|
+
if default_params.is_a?(Proc)
|
251
|
+
default_params = default_params.call(this)
|
252
|
+
elsif default_params.is_a?(Symbol)
|
253
|
+
default_params = this.send(default_params)
|
254
|
+
end
|
255
|
+
|
256
|
+
ret.update(default_params || {})
|
257
|
+
ret.update(unused_args || {})
|
258
|
+
ret.update(params || {})
|
259
|
+
ret
|
260
|
+
end
|
261
|
+
def compose_params(params={}, unused_args={}, default_params={})
|
262
|
+
self.class.compose_params(params, unused_args, default_params, self)
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.compose_arguments(method, arg_names, *args)
|
266
|
+
arguments = {}
|
267
|
+
names = arg_names.dup + [:params, :opts]
|
268
|
+
|
269
|
+
names.each_with_index do |k, i|
|
270
|
+
arguments[k] = args[i] if args.length > i
|
271
|
+
end
|
272
|
+
arguments[:params] ||= {}
|
273
|
+
arguments[:opts] ||= {}
|
274
|
+
|
275
|
+
arguments
|
276
|
+
end
|
277
|
+
def compose_arguments(method, arg_names, *args)
|
278
|
+
self.class.compose_arguments(method, arg_names, *args)
|
279
|
+
end
|
280
|
+
|
281
|
+
def self.compose_api_path(path, arguments, this=self)
|
282
|
+
# Setup the path using the following attribute order:
|
283
|
+
# 1. Args passed in
|
284
|
+
# 2. Args on this
|
285
|
+
# 3. Args on this.class
|
286
|
+
ret = (path || this.path).dup
|
287
|
+
if ret.include?(":")
|
288
|
+
missing = Set.new
|
289
|
+
matches = ret.scan(/:([^\/]*)/).flatten.map(&:to_sym)
|
290
|
+
matches.each do |match|
|
291
|
+
value = arguments[match]
|
292
|
+
begin
|
293
|
+
value ||= this.send(match)
|
294
|
+
rescue NoMethodError
|
295
|
+
end
|
296
|
+
begin
|
297
|
+
value ||= this.class.send(match) unless this.class == Class
|
298
|
+
rescue NoMethodError
|
299
|
+
end
|
300
|
+
|
301
|
+
if value.nil?
|
302
|
+
missing << match
|
303
|
+
end
|
304
|
+
|
305
|
+
ret.sub!(match.inspect, "#{value}")
|
306
|
+
end
|
307
|
+
|
308
|
+
unless missing.empty?
|
309
|
+
raise InvalidRequestError.new("Could not determine the full URL to request. Missing the following values: #{missing.to_a.join(', ')}.")
|
310
|
+
end
|
311
|
+
end
|
312
|
+
ret
|
313
|
+
end
|
314
|
+
def compose_api_path(path, arguments)
|
315
|
+
self.class.compose_api_path(path, arguments, self)
|
316
|
+
end
|
317
|
+
|
318
|
+
def self.determine_unused_args(path, arg_names, arguments, this=self)
|
319
|
+
unused = Set.new(arg_names)
|
320
|
+
path ||= this.path
|
321
|
+
if path.include?(":")
|
322
|
+
matches = path.scan(/:([^\/]*)/).flatten.map(&:to_sym)
|
323
|
+
matches.each{ |m| unused.delete(m) }
|
324
|
+
end
|
325
|
+
ret = {}
|
326
|
+
unused.each do |arg_name|
|
327
|
+
ret[arg_name] = arguments[arg_name]
|
328
|
+
end
|
329
|
+
ret
|
330
|
+
end
|
331
|
+
def determine_unused_args(path, arg_names, arguments)
|
332
|
+
self.class.determine_unused_args(path, arg_names, arguments, self)
|
333
|
+
end
|
334
|
+
|
335
|
+
end
|
336
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Paid
|
2
|
+
class APIList < APIClass
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attribute :object
|
6
|
+
attribute :data
|
7
|
+
|
8
|
+
def [](k)
|
9
|
+
data[k]
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(k, v)
|
13
|
+
data[k]=v
|
14
|
+
end
|
15
|
+
|
16
|
+
def last
|
17
|
+
data.last
|
18
|
+
end
|
19
|
+
|
20
|
+
def length
|
21
|
+
data.length
|
22
|
+
end
|
23
|
+
|
24
|
+
def each(&blk)
|
25
|
+
data.each(&blk)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.constructor(klass)
|
29
|
+
lambda do |json|
|
30
|
+
instance = self.new
|
31
|
+
instance.json = json
|
32
|
+
|
33
|
+
json.each do |k, v|
|
34
|
+
if attribute_names.include?(k.to_sym)
|
35
|
+
if k.to_sym == :data
|
36
|
+
instance.send("#{k}=", v.map{ |i| klass.construct(i) })
|
37
|
+
else
|
38
|
+
instance.send("#{k}=", v)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
instance.clear_changed_attributes
|
43
|
+
instance
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/paid/api_resource.rb
CHANGED
@@ -1,33 +1,16 @@
|
|
1
1
|
module Paid
|
2
|
-
class APIResource <
|
3
|
-
|
4
|
-
|
5
|
-
end
|
2
|
+
class APIResource < APIClass
|
3
|
+
attribute :id
|
4
|
+
attribute :object
|
6
5
|
|
7
|
-
|
8
|
-
if self == APIResource
|
9
|
-
raise NotImplementedError.new('APIResource is an abstract class. You should perform actions on its subclasses (Transaction, Customer, etc.)')
|
10
|
-
end
|
11
|
-
require 'active_support/inflector'
|
12
|
-
"/v0/#{CGI.escape(class_name.downcase).pluralize}"
|
13
|
-
end
|
6
|
+
api_instance_method :refresh, :get, :constructor => :self
|
14
7
|
|
15
|
-
def
|
16
|
-
unless id
|
17
|
-
raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
|
8
|
+
def path(base=self.class.path)
|
9
|
+
unless id
|
10
|
+
raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has an invalid ID: #{id.inspect}", 'id')
|
18
11
|
end
|
19
|
-
"#{
|
12
|
+
"#{base}/#{id}"
|
20
13
|
end
|
21
14
|
|
22
|
-
def refresh
|
23
|
-
response, api_key = Paid.request(:get, api_url, @api_key, @retrieve_options)
|
24
|
-
refresh_from(response, api_key)
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.retrieve(id, api_key=nil)
|
28
|
-
instance = self.new(id, api_key)
|
29
|
-
instance.refresh
|
30
|
-
instance
|
31
|
-
end
|
32
15
|
end
|
33
16
|
end
|