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.
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
+