paid 1.0.1 → 1.0.2
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/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
|