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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e07a6ca29545de7c32a15851ab9742341f9c0a4
|
4
|
+
data.tar.gz: dd51cf68c35c3211960950660273101a5d306126
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a9dd416fff9f14807f3773311ffadacee30624275e1a2ea50616ecf616593894f4223a1a0f32a26a5aa94f1a475d01eeb4db4ef0d813e854bc28c80aea9009d
|
7
|
+
data.tar.gz: f3d20bde96f8e59e963236dbc97df50bcfb75969b6a32fcf7fc908c3cdf67481cbb0bf2078264842b1a4487ca28bdc91b371b182546d7c18db6964dd65e706b7
|
data/History.txt
CHANGED
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.2
|
data/lib/paid.rb
CHANGED
@@ -10,226 +10,51 @@ require 'base64'
|
|
10
10
|
# Version
|
11
11
|
require 'paid/version'
|
12
12
|
|
13
|
-
# Resources
|
14
|
-
require 'paid/api_class'
|
15
|
-
require 'paid/api_resource'
|
16
|
-
require 'paid/api_singleton'
|
17
|
-
require 'paid/api_list'
|
18
|
-
require 'paid/util'
|
19
|
-
|
20
|
-
# Requires for classes
|
21
|
-
require 'paid/transaction'
|
22
|
-
require 'paid/invoice'
|
23
|
-
require 'paid/event'
|
24
|
-
require 'paid/customer'
|
25
|
-
require 'paid/plan'
|
26
|
-
require 'paid/subscription'
|
27
|
-
require 'paid/account'
|
28
|
-
|
29
13
|
# Errors
|
30
14
|
require 'paid/errors/paid_error'
|
31
15
|
require 'paid/errors/api_error'
|
32
16
|
require 'paid/errors/api_connection_error'
|
33
|
-
require 'paid/errors/invalid_request_error'
|
34
17
|
require 'paid/errors/authentication_error'
|
35
18
|
|
36
|
-
|
37
|
-
|
38
|
-
@api_key = nil
|
39
|
-
|
40
|
-
class << self
|
41
|
-
attr_accessor :api_key, :api_base, :api_test
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.api_url(path='')
|
45
|
-
"#{@api_base}#{path}"
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.request(method, path, params={}, headers={})
|
49
|
-
verify_api_key(api_key)
|
50
|
-
|
51
|
-
url = api_url(path)
|
52
|
-
|
53
|
-
request_opts = { :verify_ssl => false }
|
54
|
-
|
55
|
-
if [:get, :head, :delete].include?(method.to_s.downcase.to_sym)
|
56
|
-
unless params.empty?
|
57
|
-
url += URI.parse(url).query ? '&' : '?' + Util.query_string(params)
|
58
|
-
end
|
59
|
-
params = nil
|
60
|
-
end
|
61
|
-
|
62
|
-
headers = default_headers.update(basic_auth_headers(api_key)).update(headers)
|
63
|
-
request_opts.update(:headers => headers,
|
64
|
-
:method => method,
|
65
|
-
:open_timeout => 30,
|
66
|
-
:payload => params,
|
67
|
-
:url => url,
|
68
|
-
:timeout => 60)
|
69
|
-
|
70
|
-
begin
|
71
|
-
response = execute_request(request_opts)
|
72
|
-
rescue Exception => e
|
73
|
-
handle_request_error(e, url)
|
74
|
-
end
|
75
|
-
|
76
|
-
parse(response)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Mostly here for stubbing out during tests.
|
80
|
-
def self.execute_request(opts)
|
81
|
-
RestClient::Request.execute(opts)
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.parse(response)
|
85
|
-
begin
|
86
|
-
json = JSON.parse(response.body)
|
87
|
-
rescue JSON::ParserError
|
88
|
-
raise APIError.generic(response.code, response.body)
|
89
|
-
end
|
90
|
-
|
91
|
-
# TODO(jonclahoun): Remove this when Paid's API returns the correct status code.
|
92
|
-
json = Util.symbolize_keys(json)
|
93
|
-
if json.has_key?(:error)
|
94
|
-
raise PaidError.new(json[:error][:message], response.code, response.body, json)
|
95
|
-
end
|
96
|
-
json
|
97
|
-
end
|
98
|
-
|
99
|
-
def self.default_headers
|
100
|
-
headers = {
|
101
|
-
:user_agent => "Paid/::API_VERSION:: RubyBindings/#{Paid::VERSION}",
|
102
|
-
:content_type => 'application/x-www-form-urlencoded'
|
103
|
-
}
|
104
|
-
|
105
|
-
begin
|
106
|
-
headers.update(:x_paid_client_user_agent => JSON.generate(user_agent))
|
107
|
-
rescue => e
|
108
|
-
headers.update(:x_paid_client_raw_user_agent => user_agent.inspect,
|
109
|
-
:error => "#{e} (#{e.class})")
|
110
|
-
end
|
111
|
-
headers
|
112
|
-
end
|
113
|
-
|
114
|
-
def self.basic_auth_headers(api_key=@api_key)
|
115
|
-
api_key ||= @api_key
|
116
|
-
unless api_key
|
117
|
-
raise ArgumentError.new('No API key provided. Set your API key using "Paid.api_key = <API-KEY>".')
|
118
|
-
end
|
119
|
-
|
120
|
-
base_64_key = Base64.encode64("#{api_key}:")
|
121
|
-
{
|
122
|
-
"Authorization" => "Basic #{base_64_key}",
|
123
|
-
}
|
124
|
-
end
|
125
|
-
|
126
|
-
def self.user_agent
|
127
|
-
@uname ||= get_uname
|
128
|
-
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
129
|
-
|
130
|
-
{
|
131
|
-
:bindings_version => Paid::VERSION,
|
132
|
-
:lang => 'ruby',
|
133
|
-
:lang_version => lang_version,
|
134
|
-
:platform => RUBY_PLATFORM,
|
135
|
-
:publisher => 'paid',
|
136
|
-
:uname => @uname
|
137
|
-
}
|
138
|
-
end
|
139
|
-
|
140
|
-
def self.get_uname
|
141
|
-
`uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
|
142
|
-
rescue Errno::ENOMEM => ex # couldn't create subprocess
|
143
|
-
"uname lookup failed"
|
144
|
-
end
|
145
|
-
|
146
|
-
def self.verify_api_key(api_key)
|
147
|
-
unless api_key
|
148
|
-
raise AuthenticationError.new('No API key provided. ' +
|
149
|
-
'Set your API key using "Paid.api_key = <API-KEY>". ' +
|
150
|
-
'You can generate API keys from the Paid web interface. ' +
|
151
|
-
'See http://docs.paidapi.com/#authentication for details, or email hello@paidapi.com ' +
|
152
|
-
'if you have any questions.')
|
153
|
-
end
|
154
|
-
|
155
|
-
if api_key =~ /\s/
|
156
|
-
raise AuthenticationError.new('Your API key is invalid, as it contains ' +
|
157
|
-
'whitespace. (HINT: You can double-check your API key from the ' +
|
158
|
-
'Paid web interface. See http://docs.paidapi.com/#authentication for details, or ' +
|
159
|
-
'email hello@paidapi.com if you have any questions.)')
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def self.handle_request_error(error, url)
|
164
|
-
# First we see if this is an error with a response, and if it is
|
165
|
-
# we check to see if there is an http code and body to work with.
|
166
|
-
if error.is_a?(RestClient::ExceptionWithResponse)
|
167
|
-
if error.http_code && error.http_body
|
168
|
-
handle_api_error(error.http_code, error.http_body)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# If we got here then the error hasn't been handled yet.
|
173
|
-
# Handle it as a connection error.
|
174
|
-
handle_connection_error(error, url)
|
175
|
-
|
176
|
-
# Finally if we get here we don't know what type of error it is, so just raise it.
|
177
|
-
raise error
|
178
|
-
end
|
179
|
-
|
180
|
-
def self.handle_connection_error(error, url)
|
181
|
-
message = "An error occurred while connecting to Paid at #{url}."
|
19
|
+
# Wrapper around RestClient
|
20
|
+
require 'paid/requester'
|
182
21
|
|
183
|
-
|
184
|
-
|
185
|
-
|
22
|
+
# Builders for creating API methods.
|
23
|
+
require 'paid/path_builder'
|
24
|
+
require 'paid/headers_builder'
|
25
|
+
require 'paid/params_builder'
|
26
|
+
require 'paid/api_method'
|
186
27
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
when RestClient::SSLCertificateNotVerified
|
192
|
-
message = "Could not verify Paid's SSL certificate. " \
|
193
|
-
"Please make sure that your network is not intercepting certificates. " \
|
194
|
-
"(Try going to https://api.paidapi.com/v0 in your browser.) " \
|
195
|
-
"If this problem persists, let us know at hello@paidapi.com."
|
196
|
-
|
197
|
-
when SocketError
|
198
|
-
message = "Unexpected error when trying to connect to Paid. " \
|
199
|
-
"You may be seeing this message because your DNS is not working. " \
|
200
|
-
"To check, try running 'host api.paidapi.com' from the command line."
|
28
|
+
# Generic resources
|
29
|
+
require 'paid/api_resource'
|
30
|
+
require 'paid/api_list'
|
31
|
+
require 'paid/util'
|
201
32
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
33
|
+
# API specific resources
|
34
|
+
require 'paid/account'
|
35
|
+
require 'paid/customer'
|
36
|
+
require 'paid/event'
|
37
|
+
require 'paid/event_data'
|
38
|
+
require 'paid/invoice'
|
39
|
+
require 'paid/plan'
|
40
|
+
require 'paid/subscription'
|
41
|
+
require 'paid/transaction'
|
42
|
+
require 'paid/refund_list'
|
206
43
|
|
207
|
-
|
208
|
-
|
44
|
+
module Paid
|
45
|
+
@api_key = nil
|
209
46
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
47
|
+
@api_base = "https://api.paidapi.com/v0"
|
48
|
+
@api_staging = "https://api-staging.paidapi.com/v0"
|
49
|
+
@auth_header = nil
|
50
|
+
@api_version = "v0"
|
51
|
+
@support_email = "support@paidapi.com"
|
52
|
+
@docs_url = "https://paidapi.com/docs"
|
215
53
|
|
216
|
-
def self.handle_api_error(rcode, rbody)
|
217
|
-
begin
|
218
|
-
error_obj = JSON.parse(rbody)
|
219
|
-
rescue JSON::ParserError
|
220
|
-
raise APIError.generic(rcode, rbody)
|
221
|
-
end
|
222
|
-
error_obj = Util.symbolize_keys(error_obj)
|
223
|
-
raise APIError.generic(rcode, rbody) unless error = error_obj[:error]
|
224
54
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
when 401
|
229
|
-
raise AuthenticationError.new(error[:message], rcode, rbody, error_obj)
|
230
|
-
else
|
231
|
-
raise APIError.new(error[:message], rcode, rbody, error_obj)
|
232
|
-
end
|
55
|
+
class << self
|
56
|
+
attr_accessor :api_key, :api_base, :api_version
|
57
|
+
attr_reader :auth_header, :support_email, :docs_url, :api_staging
|
233
58
|
end
|
234
59
|
|
235
60
|
end
|
data/lib/paid/account.rb
CHANGED
@@ -1,17 +1,29 @@
|
|
1
1
|
module Paid
|
2
|
-
class Account <
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
class Account < APIResource
|
3
|
+
attr_reader :id
|
4
|
+
attr_reader :object
|
5
|
+
attr_accessor :business_name
|
6
|
+
attr_accessor :business_url
|
7
|
+
attr_accessor :business_logo
|
8
8
|
|
9
|
-
|
9
|
+
def self.retrieve(params={}, headers={})
|
10
|
+
method = APIMethod.new(:get, "/account", params, headers, self)
|
11
|
+
self.new(method.execute, method)
|
12
|
+
end
|
10
13
|
|
11
|
-
def
|
12
|
-
"/
|
14
|
+
def refresh(params={}, headers={})
|
15
|
+
method = APIMethod.new(:get, "/account", params, headers, self)
|
16
|
+
self.refresh_from(method.execute, method)
|
13
17
|
end
|
14
18
|
|
15
|
-
|
19
|
+
# Everything below here is used behind the scenes.
|
20
|
+
APIResource.register_api_subclass(self, "account")
|
21
|
+
@api_attributes = {
|
22
|
+
:id => { :readonly => true },
|
23
|
+
:object => { :readonly => true },
|
24
|
+
:business_name => nil,
|
25
|
+
:business_url => nil,
|
26
|
+
:business_logo => nil
|
27
|
+
}
|
16
28
|
end
|
17
29
|
end
|
data/lib/paid/api_list.rb
CHANGED
@@ -1,9 +1,46 @@
|
|
1
1
|
module Paid
|
2
|
-
class APIList <
|
2
|
+
class APIList < APIResource
|
3
3
|
include Enumerable
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
attr_reader :data
|
6
|
+
attr_reader :klass
|
7
|
+
|
8
|
+
def initialize(klass, json={}, api_method=nil)
|
9
|
+
if klass.is_a?(Class)
|
10
|
+
@klass = klass
|
11
|
+
else
|
12
|
+
@klass = Util.constantize(klass)
|
13
|
+
end
|
14
|
+
|
15
|
+
refresh_from(json)
|
16
|
+
end
|
17
|
+
|
18
|
+
def refresh_from(json, api_method=nil)
|
19
|
+
unless json.is_a?(Hash)
|
20
|
+
json = {
|
21
|
+
:data => json
|
22
|
+
}
|
23
|
+
end
|
24
|
+
json = Util.symbolize_keys(json)
|
25
|
+
|
26
|
+
clear_api_attributes
|
27
|
+
@api_method = api_method
|
28
|
+
@data = []
|
29
|
+
@json = Util.sorta_deep_clone(json)
|
30
|
+
|
31
|
+
json.each do |k, v|
|
32
|
+
if k.to_sym == :data
|
33
|
+
if v.respond_to?(:map)
|
34
|
+
instance_variable_set("@#{k}", v.map{ |i| @klass.new(i) })
|
35
|
+
else
|
36
|
+
instance_variable_set("@#{k}", v || [])
|
37
|
+
end
|
38
|
+
elsif self.class.api_attribute_names.include?(k)
|
39
|
+
instance_variable_set("@#{k}", determine_api_attribute_value(k, v))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
7
44
|
|
8
45
|
def [](k)
|
9
46
|
data[k]
|
@@ -25,23 +62,25 @@ module Paid
|
|
25
62
|
data.each(&blk)
|
26
63
|
end
|
27
64
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
instance.json = json
|
65
|
+
def inspect
|
66
|
+
"#<#{self.class}[#{@klass}]:0x#{self.object_id.to_s(16)}> Data: " + JSON.pretty_generate(inspect_data)
|
67
|
+
end
|
32
68
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
69
|
+
def inspect_data
|
70
|
+
ret = []
|
71
|
+
data.each do |d|
|
72
|
+
if d.respond_to?(:inspect_nested)
|
73
|
+
ret << d.inspect_nested
|
74
|
+
else
|
75
|
+
ret << d
|
41
76
|
end
|
42
|
-
instance.clear_changed_attributes
|
43
|
-
instance
|
44
77
|
end
|
78
|
+
ret
|
45
79
|
end
|
80
|
+
|
81
|
+
|
82
|
+
@api_attributes = {
|
83
|
+
:data => { :readonly => true }
|
84
|
+
}
|
46
85
|
end
|
47
86
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Paid
|
2
|
+
class APIMethod
|
3
|
+
|
4
|
+
attr_accessor :path
|
5
|
+
attr_accessor :method
|
6
|
+
attr_accessor :params
|
7
|
+
attr_accessor :headers
|
8
|
+
|
9
|
+
attr_accessor :response_body
|
10
|
+
attr_accessor :response_code
|
11
|
+
attr_accessor :error
|
12
|
+
|
13
|
+
attr_accessor :api_key
|
14
|
+
attr_accessor :api_base
|
15
|
+
|
16
|
+
def initialize(method, path, params, headers, object, api_key=nil, api_base=nil)
|
17
|
+
@api_key = api_key || Paid.api_key
|
18
|
+
@api_base = api_base || Paid.api_base
|
19
|
+
|
20
|
+
@method = method.to_sym
|
21
|
+
@path = PathBuilder.build(path, object, params)
|
22
|
+
@params = ParamsBuilder.build(params)
|
23
|
+
@headers = HeadersBuilder.build(headers, @api_key, Paid.auth_header)
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute
|
27
|
+
begin
|
28
|
+
response = Requester.request(method, url, params, headers)
|
29
|
+
@response_body = response.body
|
30
|
+
@response_code = response.code
|
31
|
+
rescue Exception => e
|
32
|
+
@response_body = e.http_body if e.respond_to?(:http_body)
|
33
|
+
@response_code = e.http_code if e.respond_to?(:http_code)
|
34
|
+
@error = compose_error(e)
|
35
|
+
raise @error
|
36
|
+
end
|
37
|
+
|
38
|
+
response_json
|
39
|
+
end
|
40
|
+
|
41
|
+
def url
|
42
|
+
"#{api_base}#{@path}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def response_json
|
46
|
+
begin
|
47
|
+
json = Util.symbolize_keys(JSON.parse(@response_body))
|
48
|
+
rescue JSON::ParserError
|
49
|
+
raise APIError.new("Unable to parse the server response as JSON.", self)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def compose_error(error)
|
54
|
+
msg = "An error occured while making the API call."
|
55
|
+
|
56
|
+
case error
|
57
|
+
when RestClient::ExceptionWithResponse
|
58
|
+
return error_with_response(error)
|
59
|
+
|
60
|
+
when RestClient::RequestTimeout
|
61
|
+
msg = "The request timed out while making the API call."
|
62
|
+
|
63
|
+
when RestClient::ServerBrokeConnection
|
64
|
+
msg = "The connection to the server broke before the request completed."
|
65
|
+
|
66
|
+
when SocketError
|
67
|
+
msg = "An unexpected error occured while trying to connect to " \
|
68
|
+
"the API. You may be seeing this message because your DNS is " \
|
69
|
+
"not working. To check, try running 'host #{Paid.api_base}' "\
|
70
|
+
"from the command line."
|
71
|
+
|
72
|
+
else
|
73
|
+
msg = "An unexpected error occured. If this problem persists let us " \
|
74
|
+
"know at #{Paid.support_email}."
|
75
|
+
end
|
76
|
+
|
77
|
+
return APIConnectionError.new(msg, self)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Handle a few common cases.
|
81
|
+
def error_with_response(error)
|
82
|
+
case @response_code
|
83
|
+
when 400, 404
|
84
|
+
return APIError.new("Invalid request. Please check the URL and parameters.", self)
|
85
|
+
when 401
|
86
|
+
return AuthenticationError.new("Authentication failed. Please check your API key and verify that it is correct.", self)
|
87
|
+
else
|
88
|
+
return APIError.new("An error occured while making the API call.", self)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|