paid 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.txt +6 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/paid.rb +32 -207
- data/lib/paid/account.rb +22 -10
- data/lib/paid/api_list.rb +56 -17
- data/lib/paid/api_method.rb +93 -0
- data/lib/paid/api_resource.rb +130 -8
- data/lib/paid/customer.rb +104 -40
- data/lib/paid/errors/api_connection_error.rb +1 -1
- data/lib/paid/errors/api_error.rb +25 -3
- data/lib/paid/errors/authentication_error.rb +1 -1
- data/lib/paid/errors/paid_error.rb +2 -9
- data/lib/paid/event.rb +28 -17
- data/lib/paid/event_data.rb +10 -0
- data/lib/paid/headers_builder.rb +75 -0
- data/lib/paid/invoice.rb +55 -16
- data/lib/paid/params_builder.rb +26 -0
- data/lib/paid/path_builder.rb +38 -0
- data/lib/paid/plan.rb +38 -13
- data/lib/paid/refund_list.rb +26 -0
- data/lib/paid/requester.rb +97 -0
- data/lib/paid/subscription.rb +47 -16
- data/lib/paid/transaction.rb +64 -16
- data/lib/paid/util.rb +14 -40
- data/paid.gemspec +1 -1
- data/test/paid/{api_class_test.rb → _api_resource_test.rb} +31 -17
- data/test/paid/account_test.rb +3 -3
- data/test/paid/api_list_test.rb +14 -8
- data/test/paid/api_method_test.rb +89 -0
- data/test/paid/customer_test.rb +20 -10
- data/test/paid/event_test.rb +3 -4
- data/test/paid/headers_builder_test.rb +39 -0
- data/test/paid/invoice_test.rb +3 -3
- data/test/paid/params_builder_test.rb +57 -0
- data/test/paid/path_builder_test.rb +67 -0
- data/test/paid/plan_test.rb +3 -3
- data/test/paid/requester_test.rb +86 -0
- data/test/paid/subscription_test.rb +3 -3
- data/test/paid/transaction_test.rb +4 -4
- data/test/paid/util_test.rb +36 -35
- data/test/test_data.rb +9 -2
- data/test/test_helper.rb +14 -14
- metadata +23 -19
- data/lib/paid/api_class.rb +0 -338
- data/lib/paid/api_singleton.rb +0 -5
- data/lib/paid/errors/invalid_request_error.rb +0 -10
- data/test/mock_resource.rb +0 -69
- data/test/paid/api_resource_test.rb +0 -28
- data/test/paid/api_singleton_test.rb +0 -12
- data/test/paid/authentication_test.rb +0 -50
- data/test/paid/status_codes_test.rb +0 -63
data/lib/paid/api_resource.rb
CHANGED
@@ -1,16 +1,138 @@
|
|
1
1
|
module Paid
|
2
|
-
class APIResource
|
3
|
-
attribute :id
|
4
|
-
attribute :object
|
2
|
+
class APIResource
|
5
3
|
|
6
|
-
|
4
|
+
class << self
|
5
|
+
# The default :id method is deprecated and isn't useful to us
|
6
|
+
if method_defined?(:id)
|
7
|
+
undef :id
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# The default :id method is deprecated and isn't useful to us
|
12
|
+
if method_defined?(:id)
|
13
|
+
undef :id
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :api_method
|
17
|
+
attr_reader :json
|
18
|
+
|
19
|
+
def initialize(json=nil, api_method=nil)
|
20
|
+
refresh_from(json)
|
21
|
+
end
|
22
|
+
|
23
|
+
def refresh_from(json={}, api_method=nil)
|
24
|
+
unless json.is_a?(Hash)
|
25
|
+
json = { :id => json }
|
26
|
+
end
|
27
|
+
json = Util.symbolize_keys(json)
|
28
|
+
|
29
|
+
# Clear or write over any old data
|
30
|
+
clear_api_attributes
|
31
|
+
@api_method = api_method
|
32
|
+
@json = Util.sorta_deep_clone(json)
|
33
|
+
|
34
|
+
# Use json (not the @json, the cloned copy)
|
35
|
+
json.each do |k, v|
|
36
|
+
if self.class.api_attribute_names.include?(k.to_sym)
|
37
|
+
instance_variable_set("@#{k}", determine_api_attribute_value(k, v))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
45
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> Attributes: " + JSON.pretty_generate(inspect_api_attributes)
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect_nested
|
49
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
50
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}>"
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_json(*args)
|
54
|
+
JSON.generate(api_attributes)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.api_attribute_names
|
58
|
+
@api_attributes.map(&:first)
|
59
|
+
end
|
60
|
+
|
61
|
+
def api_attributes
|
62
|
+
ret = {}
|
63
|
+
self.class.api_attribute_names.each do |attribute|
|
64
|
+
ret[attribute] = self.send(attribute)
|
65
|
+
end
|
66
|
+
ret
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect_api_attributes
|
70
|
+
ret = {}
|
71
|
+
api_attributes.each do |k, v|
|
72
|
+
if v.is_a?(APIResource)
|
73
|
+
ret[k] = v.inspect_nested
|
74
|
+
else
|
75
|
+
ret[k] = v
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ret
|
79
|
+
end
|
80
|
+
|
81
|
+
# TODO(joncalhoun): Make this work for nested class construction.
|
82
|
+
def changed_api_attributes
|
83
|
+
ret = {}
|
84
|
+
self.api_attributes.each do |name, value|
|
85
|
+
if @json[name] != value
|
86
|
+
ret[name] = value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
ret
|
90
|
+
end
|
91
|
+
|
92
|
+
def clear_api_attributes
|
93
|
+
self.class.api_attribute_names.each do |name|
|
94
|
+
instance_variable_set("@#{name}", nil)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.determine_api_attribute_value(name, raw_value)
|
99
|
+
if @api_attributes[name] && @api_attributes[name].has_key?(:constructor)
|
100
|
+
klass = Util.constantize(@api_attributes[name][:constructor])
|
101
|
+
if(klass.respond_to?(:construct))
|
102
|
+
klass.construct(raw_value)
|
103
|
+
else
|
104
|
+
klass.new(raw_value)
|
105
|
+
end
|
106
|
+
else
|
107
|
+
raw_value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
def determine_api_attribute_value(name, raw_value)
|
111
|
+
self.class.determine_api_attribute_value(name, raw_value)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def self.api_subclasses
|
116
|
+
return @api_subclasses ||= Set.new
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.api_subclass_fetch(name)
|
120
|
+
@api_subclasses_hash ||= {}
|
121
|
+
if @api_subclasses_hash.has_key?(name)
|
122
|
+
@api_subclasses_hash[name]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.register_api_subclass(subclass, name=nil)
|
127
|
+
@api_subclasses ||= Set.new
|
128
|
+
@api_subclasses << subclass
|
7
129
|
|
8
|
-
|
9
|
-
|
10
|
-
|
130
|
+
unless name.nil?
|
131
|
+
@api_subclasses_hash ||= {}
|
132
|
+
@api_subclasses_hash[name] = subclass
|
11
133
|
end
|
12
|
-
"#{base}/#{id}"
|
13
134
|
end
|
14
135
|
|
136
|
+
@api_attributes = {}
|
15
137
|
end
|
16
138
|
end
|
data/lib/paid/customer.rb
CHANGED
@@ -1,44 +1,108 @@
|
|
1
1
|
module Paid
|
2
2
|
class Customer < APIResource
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
3
|
+
attr_reader :id
|
4
|
+
attr_reader :object
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :email
|
7
|
+
attr_accessor :description
|
8
|
+
attr_accessor :external_id
|
9
|
+
attr_accessor :phone
|
10
|
+
attr_accessor :address_line1
|
11
|
+
attr_accessor :address_line2
|
12
|
+
attr_accessor :address_city
|
13
|
+
attr_accessor :address_state
|
14
|
+
attr_accessor :address_zip
|
15
|
+
attr_accessor :allow_ach
|
16
|
+
attr_accessor :allow_wire
|
17
|
+
attr_accessor :allow_check
|
18
|
+
attr_accessor :allow_credit_card
|
19
|
+
attr_accessor :terms
|
20
|
+
attr_accessor :billing_type
|
21
|
+
attr_accessor :billing_cycle
|
22
|
+
attr_accessor :stripe_customer_id
|
23
|
+
|
24
|
+
def self.all(params={}, headers={})
|
25
|
+
method = APIMethod.new(:get, "/customers", params, headers, self)
|
26
|
+
APIList.new(self, method.execute, method)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.retrieve(id, params={}, headers={})
|
30
|
+
params = ParamsBuilder.merge(params, {
|
31
|
+
:id => id
|
32
|
+
})
|
33
|
+
method = APIMethod.new(:get, "/customers/:id", params, headers, self)
|
34
|
+
self.new(method.execute, method)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.by_external_id(external_id, params={}, headers={})
|
38
|
+
params = ParamsBuilder.merge(params, {
|
39
|
+
:external_id => external_id
|
40
|
+
})
|
41
|
+
method = APIMethod.new(:get, "/customers/by_external_id", params, headers, self)
|
42
|
+
self.new(method.execute, method)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.create(params={}, headers={})
|
46
|
+
method = APIMethod.new(:post, "/customers", params, headers, self)
|
47
|
+
self.new(method.execute, method)
|
48
|
+
end
|
49
|
+
|
50
|
+
def refresh(params={}, headers={})
|
51
|
+
method = APIMethod.new(:get, "/customers/:id", params, headers, self)
|
52
|
+
self.refresh_from(method.execute, method)
|
53
|
+
end
|
54
|
+
|
55
|
+
def save(params={}, headers={})
|
56
|
+
params = ParamsBuilder.merge(params, changed_api_attributes)
|
57
|
+
method = APIMethod.new(:put, "/customers/:id", params, headers, self)
|
58
|
+
self.refresh_from(method.execute, method)
|
59
|
+
end
|
60
|
+
|
61
|
+
def generate_invoice(params={}, headers={})
|
62
|
+
method = APIMethod.new(:post, "/customers/:id/generate_invoice", params, headers, self)
|
63
|
+
Util.constantize(:Invoice).new(method.execute, method)
|
64
|
+
end
|
65
|
+
|
66
|
+
def invoices(params={}, headers={})
|
67
|
+
params = ParamsBuilder.merge(params, {
|
68
|
+
:customer => self.id,
|
69
|
+
})
|
70
|
+
method = APIMethod.new(:get, "/invoices", params, headers, self)
|
71
|
+
APIList.new(:Invoice, method.execute, method)
|
72
|
+
end
|
73
|
+
|
74
|
+
def transactions(params={}, headers={})
|
75
|
+
params = ParamsBuilder.merge(params, {
|
76
|
+
:customer => self.id,
|
77
|
+
})
|
78
|
+
method = APIMethod.new(:get, "/transactions", params, headers, self)
|
79
|
+
APIList.new(:Transaction, method.execute, method)
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Everything below here is used behind the scenes.
|
84
|
+
APIResource.register_api_subclass(self, "customer")
|
85
|
+
@api_attributes = {
|
86
|
+
:id => { :readonly => true },
|
87
|
+
:object => { :readonly => true },
|
88
|
+
:name => nil,
|
89
|
+
:email => nil,
|
90
|
+
:description => nil,
|
91
|
+
:external_id => nil,
|
92
|
+
:phone => nil,
|
93
|
+
:address_line1 => nil,
|
94
|
+
:address_line2 => nil,
|
95
|
+
:address_city => nil,
|
96
|
+
:address_state => nil,
|
97
|
+
:address_zip => nil,
|
98
|
+
:allow_ach => nil,
|
99
|
+
:allow_wire => nil,
|
100
|
+
:allow_check => nil,
|
101
|
+
:allow_credit_card => nil,
|
102
|
+
:terms => nil,
|
103
|
+
:billing_type => nil,
|
104
|
+
:billing_cycle => nil,
|
105
|
+
:stripe_customer_id => nil
|
106
|
+
}
|
43
107
|
end
|
44
108
|
end
|
@@ -1,10 +1,32 @@
|
|
1
1
|
module Paid
|
2
2
|
class APIError < PaidError
|
3
|
+
attr_reader :api_method
|
3
4
|
|
4
|
-
def
|
5
|
-
|
6
|
-
|
5
|
+
def initialize(message=nil, api_method=nil)
|
6
|
+
@message = message
|
7
|
+
@api_method = api_method
|
7
8
|
end
|
8
9
|
|
10
|
+
def code
|
11
|
+
@api_method.response_code if @api_method
|
12
|
+
end
|
13
|
+
|
14
|
+
def body
|
15
|
+
@api_method.response_body if @api_method
|
16
|
+
end
|
17
|
+
|
18
|
+
def json
|
19
|
+
begin
|
20
|
+
@api_method.response_json if @api_method
|
21
|
+
rescue APIError
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
prefix = code.nil? ? "" : "(Status #{code}) "
|
28
|
+
"#{prefix}#{@message}"
|
29
|
+
end
|
9
30
|
end
|
10
31
|
end
|
32
|
+
|
@@ -1,20 +1,13 @@
|
|
1
1
|
module Paid
|
2
2
|
class PaidError < StandardError
|
3
3
|
attr_reader :message
|
4
|
-
attr_reader :http_status
|
5
|
-
attr_reader :http_body
|
6
|
-
attr_reader :json_body
|
7
4
|
|
8
|
-
def initialize(message=nil
|
5
|
+
def initialize(message=nil)
|
9
6
|
@message = message
|
10
|
-
@http_status = http_status
|
11
|
-
@http_body = http_body
|
12
|
-
@json_body = json_body
|
13
7
|
end
|
14
8
|
|
15
9
|
def to_s
|
16
|
-
|
17
|
-
"#{status_string}#{@message}"
|
10
|
+
"#{@message}"
|
18
11
|
end
|
19
12
|
end
|
20
13
|
end
|
data/lib/paid/event.rb
CHANGED
@@ -1,26 +1,37 @@
|
|
1
1
|
module Paid
|
2
2
|
class Event < APIResource
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
# attributes :id and :object inherited from APIResource
|
12
|
-
attribute :created_at
|
13
|
-
attribute :type
|
14
|
-
attribute :data, Data
|
3
|
+
attr_reader :id
|
4
|
+
attr_reader :object
|
5
|
+
attr_reader :created_at
|
6
|
+
attr_reader :type
|
7
|
+
attr_reader :data
|
15
8
|
|
16
|
-
|
17
|
-
|
9
|
+
def self.all(params={}, headers={})
|
10
|
+
method = APIMethod.new(:get, "/events", params, headers, self)
|
11
|
+
APIList.new(self, method.execute, method)
|
12
|
+
end
|
18
13
|
|
14
|
+
def self.retrieve(id, params={}, headers={})
|
15
|
+
params = ParamsBuilder.merge(params, {
|
16
|
+
:id => id
|
17
|
+
})
|
18
|
+
method = APIMethod.new(:get, "/events/:id", params, headers, self)
|
19
|
+
self.new(method.execute, method)
|
20
|
+
end
|
19
21
|
|
20
|
-
def
|
21
|
-
"/
|
22
|
+
def refresh(params={}, headers={})
|
23
|
+
method = APIMethod.new(:get, "/events/:id", params, headers, self)
|
24
|
+
self.refresh_from(method.execute, method)
|
22
25
|
end
|
23
26
|
|
24
|
-
|
27
|
+
# Everything below here is used behind the scenes.
|
28
|
+
APIResource.register_api_subclass(self, "event")
|
29
|
+
@api_attributes = {
|
30
|
+
:id => { :readonly => true },
|
31
|
+
:object => { :readonly => true },
|
32
|
+
:created_at => { :readonly => true },
|
33
|
+
:type => { :readonly => true },
|
34
|
+
:data => { :constructor => :EventData, :readonly => true },
|
35
|
+
}
|
25
36
|
end
|
26
37
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Paid
|
2
|
+
module HeadersBuilder
|
3
|
+
|
4
|
+
def self.build(headers, api_key=nil, auth_key=nil)
|
5
|
+
headers ||= {}
|
6
|
+
if auth_key
|
7
|
+
temp = default_headers.merge(custom_auth_header(auth_key, api_key))
|
8
|
+
else
|
9
|
+
temp = default_headers.merge(basic_auth_header(api_key))
|
10
|
+
end
|
11
|
+
temp.merge(headers)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.default_headers
|
15
|
+
headers = {
|
16
|
+
:user_agent => "Paid/#{Paid.api_version} RubyBindings/#{Paid::VERSION}",
|
17
|
+
:content_type => 'application/x-www-form-urlencoded'
|
18
|
+
}
|
19
|
+
|
20
|
+
begin
|
21
|
+
headers.update(:x_paid_client_user_agent => JSON.generate(user_agent))
|
22
|
+
rescue => e
|
23
|
+
headers.update(:x_paid_client_raw_user_agent => user_agent.inspect,
|
24
|
+
:error => "#{e} (#{e.class})")
|
25
|
+
end
|
26
|
+
headers
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.custom_auth_header(key, api_key)
|
30
|
+
unless api_key
|
31
|
+
raise AuthenticationError.new('No API key provided. Set your API key using "Paid.api_key = <API-KEY>".')
|
32
|
+
end
|
33
|
+
|
34
|
+
{
|
35
|
+
key => api_key,
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.basic_auth_header(api_key)
|
40
|
+
unless api_key
|
41
|
+
raise AuthenticationError.new('No API key provided. Set your API key using "Paid.api_key = <API-KEY>".')
|
42
|
+
end
|
43
|
+
|
44
|
+
base_64_key = Base64.encode64("#{api_key}:")
|
45
|
+
{
|
46
|
+
"Authorization" => "Basic #{base_64_key}",
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.user_agent
|
51
|
+
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
52
|
+
|
53
|
+
{
|
54
|
+
:bindings_version => Paid::VERSION,
|
55
|
+
:lang => 'ruby',
|
56
|
+
:lang_version => lang_version,
|
57
|
+
:platform => RUBY_PLATFORM,
|
58
|
+
:publisher => 'paid',
|
59
|
+
:uname => get_uname
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.get_uname
|
64
|
+
`uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
|
65
|
+
rescue Errno::ENOMEM => ex # couldn't create subprocess
|
66
|
+
"uname lookup failed"
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|