peakium 0.1.0 → 0.1.1

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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +11 -0
  4. data/CONTRIBUTORS +1 -0
  5. data/Gemfile +2 -0
  6. data/History.txt +0 -0
  7. data/LICENSE +21 -0
  8. data/README.rdoc +43 -0
  9. data/Rakefile +16 -0
  10. data/VERSION +1 -0
  11. data/bin/peakium-console +7 -0
  12. data/gemfiles/default-with-activesupport.gemfile +3 -0
  13. data/gemfiles/json.gemfile +4 -0
  14. data/gemfiles/yajl.gemfile +4 -0
  15. data/lib/data/ca-certificates.crt +3828 -0
  16. data/lib/peakium/api_operations/create.rb +16 -0
  17. data/lib/peakium/api_operations/delete.rb +11 -0
  18. data/lib/peakium/api_operations/list.rb +21 -0
  19. data/lib/peakium/api_operations/update.rb +17 -0
  20. data/lib/peakium/api_resource.rb +33 -0
  21. data/lib/peakium/api_resources/customer.rb +25 -0
  22. data/lib/peakium/api_resources/event.rb +28 -0
  23. data/lib/peakium/api_resources/event_webhook.rb +5 -0
  24. data/lib/peakium/api_resources/gateway.rb +19 -0
  25. data/lib/peakium/api_resources/invoice.rb +26 -0
  26. data/lib/peakium/api_resources/payment_session.rb +5 -0
  27. data/lib/peakium/api_resources/submission_form.rb +12 -0
  28. data/lib/peakium/api_resources/subscription.rb +26 -0
  29. data/lib/peakium/api_resources/webhook.rb +21 -0
  30. data/lib/peakium/errors/api_connection_error.rb +4 -0
  31. data/lib/peakium/errors/api_error.rb +4 -0
  32. data/lib/peakium/errors/authentication_error.rb +4 -0
  33. data/lib/peakium/errors/invalid_request_error.rb +10 -0
  34. data/lib/peakium/errors/peakium_error.rb +20 -0
  35. data/lib/peakium/list_object.rb +31 -0
  36. data/lib/peakium/peakium_object.rb +167 -0
  37. data/lib/peakium/util.rb +115 -0
  38. data/lib/peakium/version.rb +3 -0
  39. data/lib/peakium.rb +265 -0
  40. data/peakium.gemspec +27 -0
  41. data/test/peakium/api_resource_test.rb +315 -0
  42. data/test/peakium/customer_test.rb +21 -0
  43. data/test/peakium/event_test.rb +30 -0
  44. data/test/peakium/event_webhook_test.rb +13 -0
  45. data/test/peakium/gateway_test.rb +35 -0
  46. data/test/peakium/invoice_test.rb +21 -0
  47. data/test/peakium/list_object_test.rb +16 -0
  48. data/test/peakium/payment_session_test.rb +13 -0
  49. data/test/peakium/submission_form_test.rb +12 -0
  50. data/test/peakium/subscription_test.rb +13 -0
  51. data/test/peakium/util_test.rb +29 -0
  52. data/test/peakium/webhook_test.rb +20 -0
  53. data/test/test_helper.rb +337 -0
  54. metadata +99 -52
@@ -0,0 +1,16 @@
1
+ module Peakium
2
+ module APIOperations
3
+ module Create
4
+ module ClassMethods
5
+ def create(params={}, api_key=nil)
6
+ response, api_key = Peakium.request(:post, self.endpoint_url, api_key, params)
7
+ Util.convert_to_peakium_object(response, api_key)
8
+ end
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Peakium
2
+ module APIOperations
3
+ module Delete
4
+ def delete
5
+ response, api_key = Peakium.request(:delete, endpoint_url, @api_key)
6
+ refresh_from(response, api_key)
7
+ self
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module Peakium
2
+ module APIOperations
3
+ module List
4
+ module ClassMethods
5
+ def all(filters={}, api_key=nil)
6
+ response, api_key = Peakium.request(:get, endpoint_url, api_key, filters)
7
+ object = Util.convert_to_peakium_object(response, api_key)
8
+
9
+ # Set the URL for list objects
10
+ if object.kind_of? ListObject
11
+ object.set_endpoint_url(endpoint_url, filters)
12
+ end
13
+ end
14
+ end
15
+
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module Peakium
2
+ module APIOperations
3
+ module Update
4
+ def save
5
+ if @unsaved_values.length > 0
6
+ values = @unsaved_values.reduce({}) do |h, k|
7
+ h.update(k => @values[k].nil? ? '' : @values[k])
8
+ end
9
+ values.delete(:id)
10
+ response, api_key = Peakium.request(:post, endpoint_url, @api_key, values)
11
+ refresh_from(response, api_key)
12
+ end
13
+ self
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Peakium
2
+ class APIResource < PeakiumObject
3
+ def self.class_name
4
+ self.name.split('::')[-1]
5
+ end
6
+
7
+ def self.endpoint_url()
8
+ if self == APIResource
9
+ raise NotImplementedError.new('APIResource is an abstract class. You should perform actions on its subclasses')
10
+ end
11
+ "/v1/#{CGI.escape(Util.camel_to_snake_case(class_name))}s"
12
+ end
13
+
14
+ def endpoint_url
15
+ unless id = self['id']
16
+ raise InvalidRequestError.new("Could not determine which endpoint URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
17
+ end
18
+ "#{self.class.endpoint_url}/#{CGI.escape(id)}"
19
+ end
20
+
21
+ def refresh
22
+ response, api_key = Peakium.request(:get, endpoint_url, @api_key, @retrieve_options)
23
+ refresh_from(response, api_key)
24
+ self
25
+ end
26
+
27
+ def self.retrieve(id, api_key=nil)
28
+ instance = self.new(id, api_key)
29
+ instance.refresh
30
+ instance
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ module Peakium
2
+ class Customer < APIResource
3
+ include Peakium::APIOperations::List
4
+
5
+ def subscriptions
6
+ Subscription.all({ :customer => id }, @api_key)
7
+ end
8
+
9
+ def cancel_subscription(token)
10
+ response, api_key = Peakium.request(:delete, subscription_url(token), @api_key)
11
+ refresh_from({ :subscription => response }, api_key, true)
12
+ subscription
13
+ end
14
+
15
+ private
16
+
17
+ def subscription_url(token)
18
+ "#{subscriptions_url}/#{CGI.escape(token)}"
19
+ end
20
+
21
+ def subscriptions_url
22
+ "#{endpoint_url}/subscriptions"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ module Peakium
2
+ class Event < APIResource
3
+ include Peakium::APIOperations::List
4
+
5
+ def validate(data)
6
+ response, api_key = Peakium.request(:post, validate_url, @api_key, data)
7
+ refresh_from(response, api_key)
8
+ self
9
+ end
10
+
11
+ def send(params={})
12
+ response, api_key = Peakium.request(:post, send_url, @api_key, params)
13
+ refresh_from(response, api_key)
14
+ self
15
+ end
16
+
17
+ private
18
+
19
+ def validate_url
20
+ endpoint_url + '/validate'
21
+ end
22
+
23
+ def send_url
24
+ endpoint_url + '/send'
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module Peakium
2
+ class EventWebhook < APIResource
3
+ include Peakium::APIOperations::List
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ module Peakium
2
+ class Gateway < APIResource
3
+ include Peakium::APIOperations::Create
4
+ include Peakium::APIOperations::List
5
+ include Peakium::APIOperations::Update
6
+
7
+ def set_default()
8
+ response, api_key = Peakium.request(:post, set_default_url, @api_key)
9
+ refresh_from(response, api_key)
10
+ self
11
+ end
12
+
13
+ private
14
+
15
+ def set_default_url
16
+ endpoint_url + '/default'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module Peakium
2
+ class Invoice < APIResource
3
+ include Peakium::APIOperations::List
4
+
5
+ def overdue(params={}, api_key = nil)
6
+ params = params + { :overdue => true }
7
+ all(params, api_key);
8
+ end
9
+
10
+ def pay
11
+ response, api_key = Peakium.request(:post, pay_url, @api_key)
12
+ refresh_from(response, api_key)
13
+ self
14
+ end
15
+
16
+ private
17
+
18
+ def overdue_url
19
+ endpoint_url + '/overdue'
20
+ end
21
+
22
+ def pay_url
23
+ endpoint_url + '/pay'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ module Peakium
2
+ class PaymentSession < APIResource
3
+ include Peakium::APIOperations::List
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module Peakium
2
+ class SubmissionForm < APIResource
3
+ def self.build(type, params, api_key=nil)
4
+ response, api_key = Peakium.request(:post, self.build_url(type), api_key, params)
5
+ Util.convert_to_peakium_object(response, api_key)
6
+ end
7
+
8
+ def self.build_url(type)
9
+ "#{self.endpoint_url}/#{CGI.escape(type)}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ module Peakium
2
+ class Subscription < APIResource
3
+ include Peakium::APIOperations::List
4
+
5
+ def id
6
+ unless id = self['token']
7
+ raise InvalidRequestError.new("Object #{self.class} has not token: #{id.inspect}", id)
8
+ end
9
+ id
10
+ end
11
+
12
+ def self.retrieve(id, api_key=nil)
13
+ raise InvalidRequestError.new("You need to access individual #{self.class} through a #{Customer.class}", 'customer');
14
+ end
15
+
16
+ def endpoint_url
17
+ unless token = self['id']
18
+ raise InvalidRequestError.new("Could not determine which endpoint URL to request: #{self.class} instance has invalid token: #{token.inspect}", 'token')
19
+ end
20
+ unless customer = self['customer']
21
+ raise InvalidRequestError.new("Could not determine which endpoint URL to request: #{self.class} instance has invalid #{Customer.class}: #{customer.inspect}", 'customer')
22
+ end
23
+ url = customer.endpoint_url + "/subscriptions/#{CGI.escape(token)}"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module Peakium
2
+ class Webhook < APIResource
3
+ include Peakium::APIOperations::Create
4
+ include Peakium::APIOperations::Delete
5
+ include Peakium::APIOperations::List
6
+
7
+ def id
8
+ unless id = self['url']
9
+ raise InvalidRequestError.new("No url set for Peakium::Webhook")
10
+ end
11
+ id
12
+ end
13
+
14
+ def endpoint_url
15
+ unless url = self['url']
16
+ raise InvalidRequestError.new("Could not determine which endpoint URL to request: #{self.class} instance has invalid url: #{url.inspect}", 'url')
17
+ end
18
+ "#{self.class.endpoint_url}/#{CGI.escape(url)}"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ module Peakium
2
+ class APIConnectionError < PeakiumError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Peakium
2
+ class APIError < PeakiumError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Peakium
2
+ class AuthenticationError < PeakiumError
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ module Peakium
2
+ class InvalidRequestError < PeakiumError
3
+ attr_accessor :param
4
+
5
+ def initialize(message, param, http_status=nil, http_body=nil, json_body=nil)
6
+ super(message, http_status, http_body, json_body)
7
+ @param = param
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Peakium
2
+ class PeakiumError < StandardError
3
+ attr_reader :message
4
+ attr_reader :http_status
5
+ attr_reader :http_body
6
+ attr_reader :json_body
7
+
8
+ def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
9
+ @message = message
10
+ @http_status = http_status
11
+ @http_body = http_body
12
+ @json_body = json_body
13
+ end
14
+
15
+ def to_s
16
+ status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
17
+ "#{status_string}#{@message}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module Peakium
2
+ class ListObject < PeakiumObject
3
+
4
+ def [](k)
5
+ case k
6
+ when String, Symbol
7
+ super
8
+ else
9
+ raise ArgumentError.new("You tried to access the #{k.inspect} index, but ListObject types only support String keys. (HINT: List calls return an object with a 'data' (which is the data array). You likely want to call #data[#{k.inspect}])")
10
+ end
11
+ end
12
+
13
+ def each(&blk)
14
+ self.data.each(&blk)
15
+ end
16
+
17
+ def all(params={}, api_key=nil)
18
+ response, api_key = Peakium.request(:get, endpoint_url, api_key, params)
19
+ Util.convert_to_peakium_object(response, api_key)
20
+ self.set_endpoint_url(endpoint_url, params)
21
+ end
22
+
23
+ def set_endpoint_url(endpoint_url, params)
24
+ # Set the URL for list objects
25
+ url = endpoint_url;
26
+ url += "#{(endpoint_url.include? '?') ? '&' : '?'}#{Peakium.uri_encode(params)}" if params && params.any?
27
+ self.endpoint_url = url
28
+ self
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,167 @@
1
+ module Peakium
2
+ class PeakiumObject
3
+ include Enumerable
4
+
5
+ attr_accessor :api_key
6
+ @@permanent_attributes = Set.new([:api_key, :id])
7
+
8
+ # The default :id method is deprecated and isn't useful to us
9
+ if method_defined?(:id)
10
+ undef :id
11
+ end
12
+
13
+ def initialize(id=nil, api_key=nil)
14
+ # parameter overloading!
15
+ if id.kind_of?(Hash)
16
+ @retrieve_options = id.dup
17
+ @retrieve_options.delete(:id)
18
+ id = id[:id]
19
+ else
20
+ @retrieve_options = {}
21
+ end
22
+
23
+ @api_key = api_key
24
+ @values = {}
25
+ # This really belongs in APIResource, but not putting it there allows us
26
+ # to have a unified inspect method
27
+ @unsaved_values = Set.new
28
+ @transient_values = Set.new
29
+ @values[:id] = id if id
30
+ end
31
+
32
+ def self.construct_from(values, api_key=nil)
33
+ obj = self.new(values[:id], api_key)
34
+ obj.refresh_from(values, api_key)
35
+ obj
36
+ end
37
+
38
+ def to_s(*args)
39
+ JSON.pretty_generate(@values, :pretty => true)
40
+ end
41
+
42
+ def inspect()
43
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
44
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values, :pretty => true)
45
+ end
46
+
47
+ def refresh_from(values, api_key, partial=false)
48
+ @api_key = api_key
49
+
50
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
51
+ added = Set.new(values.keys - @values.keys)
52
+ # Wipe old state before setting new. This is useful for e.g. updating a
53
+ # customer, where there is no persistent card parameter. Mark those values
54
+ # which don't persist as transient
55
+
56
+ instance_eval do
57
+ remove_accessors(removed)
58
+ add_accessors(added)
59
+ end
60
+ removed.each do |k|
61
+ @values.delete(k)
62
+ @transient_values.add(k)
63
+ @unsaved_values.delete(k)
64
+ end
65
+ values.each do |k, v|
66
+ @values[k] = Util.convert_to_peakium_object(v, api_key)
67
+ @transient_values.delete(k)
68
+ @unsaved_values.delete(k)
69
+ end
70
+ end
71
+
72
+ def [](k)
73
+ @values[k.to_sym]
74
+ end
75
+
76
+ def []=(k, v)
77
+ send(:"#{k}=", v)
78
+ end
79
+
80
+ def keys
81
+ @values.keys
82
+ end
83
+
84
+ def values
85
+ @values.values
86
+ end
87
+
88
+ def to_json(*a)
89
+ JSON.generate(@values)
90
+ end
91
+
92
+ def as_json(*a)
93
+ @values.as_json(*a)
94
+ end
95
+
96
+ def to_hash
97
+ @values
98
+ end
99
+
100
+ def each(&blk)
101
+ @values.each(&blk)
102
+ end
103
+
104
+ protected
105
+
106
+ def metaclass
107
+ class << self; self; end
108
+ end
109
+
110
+ def remove_accessors(keys)
111
+ metaclass.instance_eval do
112
+ keys.each do |k|
113
+ next if @@permanent_attributes.include?(k)
114
+ k_eq = :"#{k}="
115
+ remove_method(k) if method_defined?(k)
116
+ remove_method(k_eq) if method_defined?(k_eq)
117
+ end
118
+ end
119
+ end
120
+
121
+ def add_accessors(keys)
122
+ metaclass.instance_eval do
123
+ keys.each do |k|
124
+ next if @@permanent_attributes.include?(k)
125
+ k_eq = :"#{k}="
126
+ define_method(k) { @values[k] }
127
+ define_method(k_eq) do |v|
128
+ if v == ""
129
+ raise ArgumentError.new(
130
+ "You cannot set #{k} to an empty string." +
131
+ "We interpret empty strings as nil in requests." +
132
+ "You may set #{self}.#{k} = nil to delete the property.")
133
+ end
134
+ @values[k] = v
135
+ @unsaved_values.add(k)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def method_missing(name, *args)
142
+ # TODO: only allow setting in updateable classes.
143
+ if name.to_s.end_with?('=')
144
+ attr = name.to_s[0...-1].to_sym
145
+ add_accessors([attr])
146
+ begin
147
+ mth = method(name)
148
+ rescue NameError
149
+ raise NoMethodError.new("Cannot set #{attr} on this object. HINT: you can't set: #{@@permanent_attributes.to_a.join(', ')}")
150
+ end
151
+ return mth.call(args[0])
152
+ else
153
+ return @values[name] if @values.has_key?(name)
154
+ end
155
+
156
+ begin
157
+ super
158
+ rescue NoMethodError => e
159
+ if @transient_values.include?(name)
160
+ raise NoMethodError.new(e.message + ". HINT: The '#{name}' attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Peakium's API, probably as a result of a save(). The attributes currently available on this object are: #{@values.keys.join(', ')}")
161
+ else
162
+ raise
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,115 @@
1
+ module Peakium
2
+ module Util
3
+ def self.objects_to_ids(h)
4
+ case h
5
+ when APIResource
6
+ h.id
7
+ when Hash
8
+ res = {}
9
+ h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
10
+ res
11
+ when Array
12
+ h.map { |v| objects_to_ids(v) }
13
+ else
14
+ h
15
+ end
16
+ end
17
+
18
+ def self.object_classes
19
+ @object_classes ||= {
20
+ 'customer' => Customer,
21
+ 'event' => Event,
22
+ 'event_webhook' => EventWebhook,
23
+ 'gateway' => Gateway,
24
+ 'invoice' => Invoice,
25
+ 'payment_session' => PaymentSession,
26
+ 'subscription' => Subscription,
27
+ 'submission_form' => SubmissionForm,
28
+ 'webhook' => Webhook,
29
+ 'list' => ListObject
30
+ }
31
+ end
32
+
33
+ def self.convert_to_peakium_object(resp, api_key)
34
+ case resp
35
+ when Array
36
+ resp.map { |i| convert_to_peakium_object(i, api_key) }
37
+ when Hash
38
+ # Try converting to a known object class. If none available, fall back to generic PeakiumObject
39
+ object_classes.fetch(resp[:object], PeakiumObject).construct_from(resp, api_key)
40
+ else
41
+ resp
42
+ end
43
+ end
44
+
45
+ def self.file_readable(file)
46
+ # This is nominally equivalent to File.readable?, but that can
47
+ # report incorrect results on some more oddball filesystems
48
+ # (such as AFS)
49
+ begin
50
+ File.open(file) { |f| }
51
+ rescue
52
+ false
53
+ else
54
+ true
55
+ end
56
+ end
57
+
58
+ def self.symbolize_names(object)
59
+ case object
60
+ when Hash
61
+ new = {}
62
+ object.each do |key, value|
63
+ key = (key.to_sym rescue key) || key
64
+ new[key] = symbolize_names(value)
65
+ end
66
+ new
67
+ when Array
68
+ object.map { |value| symbolize_names(value) }
69
+ else
70
+ object
71
+ end
72
+ end
73
+
74
+ def self.url_encode(key)
75
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
76
+ end
77
+
78
+ def self.flatten_params(params, parent_key=nil)
79
+ result = []
80
+ params.each do |key, value|
81
+ calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
82
+ if value.is_a?(Hash)
83
+ result += flatten_params(value, calculated_key)
84
+ elsif value.is_a?(Array)
85
+ result += flatten_params_array(value, calculated_key)
86
+ else
87
+ result << [calculated_key, value]
88
+ end
89
+ end
90
+ result
91
+ end
92
+
93
+ def self.flatten_params_array(value, calculated_key)
94
+ result = []
95
+ value.each do |elem|
96
+ if elem.is_a?(Hash)
97
+ result += flatten_params(elem, calculated_key)
98
+ elsif elem.is_a?(Array)
99
+ result += flatten_params_array(elem, calculated_key)
100
+ else
101
+ result << ["#{calculated_key}[]", elem]
102
+ end
103
+ end
104
+ result
105
+ end
106
+
107
+ def self.camel_to_snake_case(camel)
108
+ camel.gsub(/::/, '/').
109
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
110
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
111
+ tr("-", "_").
112
+ downcase
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,3 @@
1
+ module Peakium
2
+ VERSION = '0.1.1'
3
+ end