paid 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/History.txt +6 -0
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/lib/paid.rb +32 -207
  6. data/lib/paid/account.rb +22 -10
  7. data/lib/paid/api_list.rb +56 -17
  8. data/lib/paid/api_method.rb +93 -0
  9. data/lib/paid/api_resource.rb +130 -8
  10. data/lib/paid/customer.rb +104 -40
  11. data/lib/paid/errors/api_connection_error.rb +1 -1
  12. data/lib/paid/errors/api_error.rb +25 -3
  13. data/lib/paid/errors/authentication_error.rb +1 -1
  14. data/lib/paid/errors/paid_error.rb +2 -9
  15. data/lib/paid/event.rb +28 -17
  16. data/lib/paid/event_data.rb +10 -0
  17. data/lib/paid/headers_builder.rb +75 -0
  18. data/lib/paid/invoice.rb +55 -16
  19. data/lib/paid/params_builder.rb +26 -0
  20. data/lib/paid/path_builder.rb +38 -0
  21. data/lib/paid/plan.rb +38 -13
  22. data/lib/paid/refund_list.rb +26 -0
  23. data/lib/paid/requester.rb +97 -0
  24. data/lib/paid/subscription.rb +47 -16
  25. data/lib/paid/transaction.rb +64 -16
  26. data/lib/paid/util.rb +14 -40
  27. data/paid.gemspec +1 -1
  28. data/test/paid/{api_class_test.rb → _api_resource_test.rb} +31 -17
  29. data/test/paid/account_test.rb +3 -3
  30. data/test/paid/api_list_test.rb +14 -8
  31. data/test/paid/api_method_test.rb +89 -0
  32. data/test/paid/customer_test.rb +20 -10
  33. data/test/paid/event_test.rb +3 -4
  34. data/test/paid/headers_builder_test.rb +39 -0
  35. data/test/paid/invoice_test.rb +3 -3
  36. data/test/paid/params_builder_test.rb +57 -0
  37. data/test/paid/path_builder_test.rb +67 -0
  38. data/test/paid/plan_test.rb +3 -3
  39. data/test/paid/requester_test.rb +86 -0
  40. data/test/paid/subscription_test.rb +3 -3
  41. data/test/paid/transaction_test.rb +4 -4
  42. data/test/paid/util_test.rb +36 -35
  43. data/test/test_data.rb +9 -2
  44. data/test/test_helper.rb +14 -14
  45. metadata +23 -19
  46. data/lib/paid/api_class.rb +0 -338
  47. data/lib/paid/api_singleton.rb +0 -5
  48. data/lib/paid/errors/invalid_request_error.rb +0 -10
  49. data/test/mock_resource.rb +0 -69
  50. data/test/paid/api_resource_test.rb +0 -28
  51. data/test/paid/api_singleton_test.rb +0 -12
  52. data/test/paid/authentication_test.rb +0 -50
  53. data/test/paid/status_codes_test.rb +0 -63
@@ -1,16 +1,138 @@
1
1
  module Paid
2
- class APIResource < APIClass
3
- attribute :id
4
- attribute :object
2
+ class APIResource
5
3
 
6
- api_instance_method :refresh, :get, :constructor => :self
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
- 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')
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
- # attributes :id and :object inherited from APIResource
4
- attribute :name
5
- attribute :email
6
- attribute :description
7
- attribute :external_id
8
- attribute :phone
9
- attribute :address_line1
10
- attribute :address_line2
11
- attribute :address_city
12
- attribute :address_state
13
- attribute :address_zip
14
- attribute :allow_ach
15
- attribute :allow_wire
16
- attribute :allow_check
17
- attribute :allow_credit_card
18
- attribute :terms
19
- attribute :billing_type
20
- attribute :billing_cycle
21
- attribute :stripe_customer_id
22
-
23
- api_class_method :all, :get, :constructor => APIList.constructor(Customer)
24
- api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
25
- api_class_method :create, :post
26
- api_class_method :by_external_id, :get, ":path/by_external_id", :arguments => [:external_id]
27
-
28
- api_instance_method :save, :put, :default_params => :changed_attributes
29
- api_instance_method :generate_invoice, :post, ":path/generate_invoice", :constructor => Invoice
30
-
31
- api_instance_method :invoices, :get, Invoice.path, :default_params => :customer_id_hash, :constructor => APIList.constructor(Invoice)
32
- api_instance_method :transactions, :get, Transaction.path, :default_params => :customer_id_hash, :constructor => APIList.constructor(Transaction)
33
-
34
- def self.path
35
- "/v0/customers"
36
- end
37
-
38
- def customer_id_hash
39
- { :customer => self.id }
40
- end
41
-
42
- APIClass.register_subclass(self, "customer")
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,4 +1,4 @@
1
1
  module Paid
2
- class APIConnectionError < PaidError
2
+ class APIConnectionError < APIError
3
3
  end
4
4
  end
@@ -1,10 +1,32 @@
1
1
  module Paid
2
2
  class APIError < PaidError
3
+ attr_reader :api_method
3
4
 
4
- def self.generic(rcode, rbody)
5
- self.new("Invalid response object from API: #{rbody.inspect} " +
6
- "(HTTP response code was #{rcode})", rcode, rbody)
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,4 +1,4 @@
1
1
  module Paid
2
- class AuthenticationError < PaidError
2
+ class AuthenticationError < APIError
3
3
  end
4
4
  end
@@ -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, http_status=nil, http_body=nil, json_body=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
- status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
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
- class Data
4
- def self.construct(json={})
5
- return nil if json.nil?
6
- klass = APIClass.subclass_fetch(json[:object])
7
- klass.construct(json)
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
- api_class_method :all, :get, :constructor => APIList.constructor(Event)
17
- api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
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 self.path
21
- "/v0/events"
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
- APIClass.register_subclass(self, "event")
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,10 @@
1
+ module Paid
2
+ class EventData
3
+
4
+ def self.construct(json={})
5
+ return nil if json.nil?
6
+ return APIResource.api_subclass_fetch(json[:object]).new(json)
7
+ end
8
+
9
+ end
10
+ 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
+