privacygate 1.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_REQUEST_TEMPLATE/issue_request_template.md +36 -0
  3. data/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +51 -0
  4. data/.gitignore +51 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +21 -0
  7. data/README.md +278 -0
  8. data/Rakefile +6 -0
  9. data/examples/charge.rb +38 -0
  10. data/examples/checkout.rb +61 -0
  11. data/examples/event.rb +26 -0
  12. data/examples/webhook.rb +35 -0
  13. data/lib/privacygate/api_errors.rb +157 -0
  14. data/lib/privacygate/api_resources/base/api_object.rb +206 -0
  15. data/lib/privacygate/api_resources/base/api_resource.rb +25 -0
  16. data/lib/privacygate/api_resources/base/create.rb +15 -0
  17. data/lib/privacygate/api_resources/base/delete.rb +16 -0
  18. data/lib/privacygate/api_resources/base/list.rb +25 -0
  19. data/lib/privacygate/api_resources/base/save.rb +18 -0
  20. data/lib/privacygate/api_resources/base/update.rb +15 -0
  21. data/lib/privacygate/api_resources/charge.rb +14 -0
  22. data/lib/privacygate/api_resources/checkout.rb +19 -0
  23. data/lib/privacygate/api_resources/event.rb +13 -0
  24. data/lib/privacygate/api_response.rb +48 -0
  25. data/lib/privacygate/client.rb +120 -0
  26. data/lib/privacygate/util.rb +59 -0
  27. data/lib/privacygate/version.rb +3 -0
  28. data/lib/privacygate/webhooks.rb +52 -0
  29. data/lib/privacygate.rb +42 -0
  30. data/privacygate.gemspec +28 -0
  31. data/spec/api_resources/base/api_object_spec.rb +156 -0
  32. data/spec/api_resources/base/api_resource_spec.rb +32 -0
  33. data/spec/api_resources/charge_spec.rb +19 -0
  34. data/spec/api_resources/checkout_spec.rb +31 -0
  35. data/spec/api_resources/event_spec.rb +12 -0
  36. data/spec/endpont_spec.rb +103 -0
  37. data/spec/error_spec.rb +58 -0
  38. data/spec/response_spec.rb +43 -0
  39. data/spec/spec_helper.rb +15 -0
  40. data/spec/webhook_spec.rb +36 -0
  41. metadata +161 -0
@@ -0,0 +1,157 @@
1
+ module PrivacyGate
2
+ module Errors
3
+ class APIError < StandardError
4
+ attr_reader :message
5
+
6
+ # Response contains a PrivacyGateResponse object
7
+ attr_accessor :response
8
+
9
+ attr_reader :http_body
10
+ attr_reader :http_headers
11
+ attr_reader :http_status
12
+ attr_reader :json_body
13
+ attr_reader :request_id
14
+
15
+ # Initializes a API error.
16
+ def initialize(message = nil, http_status: nil, http_body: nil,
17
+ json_body: nil, http_headers: nil)
18
+ @message = message
19
+ @http_status = http_status
20
+ @http_body = http_body
21
+ @http_headers = http_headers || {}
22
+ @json_body = json_body
23
+ @request_id = @http_headers["x-request-id"]
24
+ end
25
+
26
+ def to_s
27
+ status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
28
+ id_string = @request_id.nil? ? "" : "(Request #{@request_id}) "
29
+ "#{status_string}#{id_string}#{@message}"
30
+ end
31
+ end
32
+
33
+ # in case error connecting to privacygate server
34
+ class APIConnectionError < APIError
35
+ end
36
+
37
+ # Status 400
38
+ class BadRequestError < APIError
39
+ end
40
+
41
+ class ParamRequiredError < APIError
42
+ end
43
+
44
+ class InvalidRequestError < APIError
45
+ end
46
+
47
+ # Status 401
48
+ class AuthenticationError < APIError
49
+ end
50
+
51
+ # Status 404
52
+ class ResourceNotFoundError < APIError
53
+ end
54
+
55
+ # Status 422
56
+ class ValidationError < APIError
57
+ end
58
+
59
+ # Status 429
60
+ class RateLimitExceededError < APIError
61
+ end
62
+
63
+ # Status 500
64
+ class InternalServerError < APIError
65
+ end
66
+
67
+ # Status 503
68
+ class ServiceUnavailableError < APIError
69
+ end
70
+
71
+ # Webhook errors
72
+ class WebhookError < APIError
73
+ attr_accessor :sig_header
74
+
75
+ def initialize(message, sig_header, http_body: nil)
76
+ super(message, http_body: http_body)
77
+ @sig_header = sig_header
78
+ end
79
+ end
80
+
81
+ class SignatureVerificationError < WebhookError
82
+ end
83
+
84
+ class WebhookInvalidPayload < WebhookError
85
+ end
86
+
87
+ # Errors handling
88
+ def self.handle_error_response(http_resp)
89
+ begin
90
+ resp = PrivacyGateResponse.from_faraday_hash(http_resp)
91
+ error_data = resp.data[:error]
92
+
93
+ raise APIError, "Unknown error" unless error_data
94
+ rescue JSON::ParserError, APIError
95
+ raise general_api_error(http_resp[:status], http_resp[:body])
96
+ end
97
+ error = specific_api_error(resp, error_data)
98
+ error.response = resp
99
+ raise(error)
100
+ end
101
+
102
+ def self.general_api_error(status, body)
103
+ APIError.new("Invalid response object from API: #{body.inspect} " +
104
+ "(HTTP response code: #{status} http_body: #{body}")
105
+ end
106
+
107
+ def self.specific_api_error(resp, error_data)
108
+ opts = {
109
+ http_body: resp.http_body,
110
+ http_headers: resp.http_headers,
111
+ http_status: resp.http_status,
112
+ json_body: resp.data,
113
+ }
114
+ case resp.http_status
115
+ when 400
116
+ # in case of known error code
117
+ case error_data[:type]
118
+ when 'param_required'
119
+ ParamRequiredError.new(error_data[:message], opts)
120
+ when 'validation_error'
121
+ ValidationError.new(error_data[:message], opts)
122
+ when 'invalid_request'
123
+ InvalidRequestError.new(error_data[:message], opts)
124
+ else
125
+ InvalidRequestError.new(error_data[:message], opts)
126
+ end
127
+ when 401 then
128
+ AuthenticationError.new(error_data[:message], opts)
129
+ when 404
130
+ ResourceNotFoundError.new(error_data[:message], opts)
131
+ when 429
132
+ RateLimitExceededError.new(error_data[:message], opts)
133
+ when 500
134
+ InternalServerError.new(error_data[:message], opts)
135
+ when 503
136
+ ServiceUnavailableError.new(error_data[:message], opts)
137
+ else
138
+ APIError.new(error_data[:message], opts)
139
+ end
140
+ end
141
+
142
+ def self.handle_network_error(e, api_base = nil)
143
+ api_base ||= @api_uri
144
+ case e
145
+ when Faraday::ConnectionFailed
146
+ message = "Unexpected error communicating when trying to connect to PrivacyGate."
147
+ when Faraday::SSLError
148
+ message = "Could not establish a secure connection to PrivacyGate."
149
+ when Faraday::TimeoutError
150
+ message = "Could not connect to PrivacyGate (#{api_base})."
151
+ else
152
+ message = "Unexpected error communicating with PrivacyGate."
153
+ end
154
+ raise APIConnectionError, message + "\n\n(Network error: #{e.message})"
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,206 @@
1
+ module PrivacyGate
2
+ module APIResources
3
+ module Base
4
+ # Base APIObject class
5
+ # Used to work and display with all the data
6
+ # that PrivacyGate API returns
7
+ class APIObject
8
+ include Enumerable
9
+
10
+ def initialize(id = nil, client = nil)
11
+ @data = {}
12
+ @data[:id] = id if id
13
+ @client = client
14
+ @unsaved_values = Set.new
15
+ @transient_values = Set.new
16
+ end
17
+
18
+ # Base object options section
19
+ def [](k)
20
+ @data[k.to_sym]
21
+ end
22
+
23
+ def []=(k, v)
24
+ send(:"#{k}=", v)
25
+ end
26
+
27
+ def keys
28
+ @data.keys
29
+ end
30
+
31
+ def values
32
+ @data.values
33
+ end
34
+
35
+ def each(&blk)
36
+ @data.each(&blk)
37
+ end
38
+
39
+ def to_s(*_args)
40
+ JSON.pretty_generate(to_hash)
41
+ end
42
+
43
+ def to_hash
44
+ @data.each_with_object({}) do |(key, value), output|
45
+ case value
46
+ when Array
47
+ output[key] = value.map {|v| v.respond_to?(:to_hash) ? v.to_hash : v}
48
+ else
49
+ output[key] = value.respond_to?(:to_hash) ? value.to_hash : value
50
+ end
51
+ end
52
+ end
53
+
54
+ def to_json(*_a)
55
+ JSON.generate(@data)
56
+ end
57
+
58
+ def inspect
59
+ item_id = respond_to?(:id) && !id.nil? ? "id=#{id}" : "No ID"
60
+ "#{self.class}: #{item_id}> Serialized: " + JSON.pretty_generate(@data)
61
+ end
62
+
63
+ def respond_to_missing?(symbol, include_private = false)
64
+ @data && @data.key?(symbol) || super
65
+ end
66
+
67
+ def method_missing(name, *args)
68
+ if name.to_s.end_with?("=")
69
+
70
+ attr = name.to_s[0...-1].to_sym
71
+ val = args.first
72
+ add_accessors([attr], attr => val)
73
+
74
+ begin
75
+ mth = method(name)
76
+ rescue NameError
77
+ raise NoMethodError, "Cannot set #{attr} on this object."
78
+ end
79
+
80
+ return mth.call(args[0])
81
+
82
+ elsif @data.key?(name)
83
+ return @data[name]
84
+ end
85
+
86
+ begin
87
+ super
88
+ rescue NoMethodError => e
89
+ raise unless @transient_values.include?(name)
90
+ raise NoMethodError, e.message + " Available attributes: #{@data.keys.join(', ')}"
91
+ end
92
+ end
93
+
94
+ # Object serialize section
95
+ def serialize_params(options = {})
96
+ update_hash = {}
97
+
98
+ @data.each do |k, v|
99
+ if options[:push] || @unsaved_values.include?(k) || v.is_a?(APIObject)
100
+ push = options[:push] || @unsaved_values.include?(k)
101
+ update_hash[k.to_sym] = serialize_params_value(@data[k], push)
102
+ end
103
+ end
104
+
105
+ update_hash.reject! {|_, v| v.nil? || v.empty?}
106
+ update_hash
107
+ end
108
+
109
+ def serialize_params_value(value, push)
110
+ if value.nil?
111
+ ""
112
+ elsif value.is_a?(Array)
113
+ value.map {|v| serialize_params_value(v, push)}
114
+
115
+ elsif value.is_a?(Hash)
116
+ Util.convert_to_api_object(value, @opts).serialize_params
117
+
118
+ elsif value.is_a?(APIObject)
119
+ value.serialize_params(push: push)
120
+ else
121
+ value
122
+ end
123
+ end
124
+
125
+ # Object initialize/update section
126
+ def self.create_from(values, client = nil)
127
+ values = Util.symbolize_names(values)
128
+ new(values[:id], client).send(:initialize_from, values)
129
+ end
130
+
131
+ def initialize_from(values, partial = false)
132
+ removed = partial ? Set.new : Set.new(@data.keys - values.keys)
133
+ added = Set.new(values.keys - @data.keys)
134
+
135
+ remove_accessors(removed)
136
+ add_accessors(added, values)
137
+
138
+ removed.each do |k|
139
+ @data.delete(k)
140
+ @transient_values.add(k)
141
+ @unsaved_values.delete(k)
142
+ end
143
+
144
+ update_attributes(values)
145
+ values.each_key do |k|
146
+ @transient_values.delete(k)
147
+ @unsaved_values.delete(k)
148
+ end
149
+
150
+ self
151
+ end
152
+
153
+ def update_attributes(values)
154
+ values.each do |k, v|
155
+ add_accessors([k], values) unless metaclass.method_defined?(k.to_sym)
156
+ @data[k] = Util.convert_to_api_object(v, @client)
157
+ @unsaved_values.add(k)
158
+ end
159
+ end
160
+
161
+
162
+ protected
163
+
164
+ def metaclass
165
+ class << self
166
+ self
167
+ end
168
+ end
169
+
170
+ def remove_accessors(keys)
171
+ metaclass.instance_eval do
172
+ keys.each do |k|
173
+ # Remove methods for the accessor's reader and writer.
174
+ [k, :"#{k}=", :"#{k}?"].each do |method_name|
175
+ remove_method(method_name) if method_defined?(method_name)
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ def add_accessors(keys, values)
182
+ metaclass.instance_eval do
183
+ keys.each do |k|
184
+ if k == :method
185
+ define_method(k) {|*args| args.empty? ? @data[k] : super(*args)}
186
+ else
187
+ define_method(k) {@data[k]}
188
+ end
189
+
190
+ define_method(:"#{k}=") do |v|
191
+ if v != ""
192
+ @data[k] = Util.convert_to_api_object(v, @opts)
193
+ @unsaved_values.add(k)
194
+ end
195
+ end
196
+
197
+ if [FalseClass, TrueClass].include?(values[k].class)
198
+ define_method(:"#{k}?") {@data[k]}
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,25 @@
1
+ module PrivacyGate
2
+ module APIResources
3
+ module Base
4
+ # Base resource class
5
+ # if you need to add additional API resource, inherit from APIResource
6
+ class APIResource < APIObject
7
+ class << self
8
+ attr_accessor :client
9
+ end
10
+
11
+ @client = nil
12
+
13
+ def self.retrieve(id, params = {})
14
+ resp = @client.request(:get, "#{self::RESOURCE_PATH}/#{id}", params)
15
+ Util.convert_to_api_object(resp.data, @client, self)
16
+ end
17
+
18
+ def refresh(params = {})
19
+ resp = @client.request(:get, "#{self.class::RESOURCE_PATH}/#{self[:id]}", params)
20
+ initialize_from(resp.data)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrivacyGate
4
+ module APIResources
5
+ module Base
6
+ # create operations mixin
7
+ module Create
8
+ def create(params = {})
9
+ response = @client.request(:post, "#{self::RESOURCE_PATH}", params)
10
+ Util.convert_to_api_object(response.data, @client, self)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrivacyGate
4
+ module APIResources
5
+ module Base
6
+ # delete opertaions mixin
7
+ module Delete
8
+ def delete
9
+ response = @client.request(:delete, "#{self.class::RESOURCE_PATH}/#{self[:id]}")
10
+ initialize_from(response.data)
11
+ self
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrivacyGate
4
+ module APIResources
5
+ module Base
6
+ # list operations mixin
7
+ module List
8
+ def list(params = {})
9
+ resp = @client.request(:get, "#{self::RESOURCE_PATH}", params)
10
+ Util.convert_to_api_object(resp.data, @client, self)
11
+ end
12
+
13
+ def auto_paging(params = {}, &blk)
14
+ loop do
15
+ page = list(params)
16
+ last_id = page.data.empty? ? nil : page.data.last.id
17
+ break if last_id.nil?
18
+ params[:starting_after] = last_id
19
+ page.data.each(&blk)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrivacyGate
4
+ module APIResources
5
+ module Base
6
+ # save operations mixin
7
+ module Save
8
+ def save
9
+ values = serialize_params(self)
10
+ values.delete(:id)
11
+ resp = @client.request(:put, "#{self.class::RESOURCE_PATH}/#{self[:id]}", self)
12
+ initialize_from(resp.data)
13
+ self
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrivacyGate
4
+ module APIResources
5
+ module Base
6
+ # update operations mixin
7
+ module Update
8
+ def modify(id, params = {})
9
+ resp = @client.request(:put, "#{self::RESOURCE_PATH}/#{id}", params)
10
+ Util.convert_to_api_object(resp.data, @client, self)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module PrivacyGate
2
+ module APIResources
3
+ # Class that allows you to work with Charge resource
4
+ class Charge < Base::APIResource
5
+ # class methods
6
+ extend Base::List
7
+ extend Base::Create
8
+
9
+ # class constants
10
+ OBJECT_NAME = "charge".freeze
11
+ RESOURCE_PATH = "charges".freeze
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module PrivacyGate
2
+ module APIResources
3
+ # Class that allows you to work with Checkout resource
4
+ class Checkout < Base::APIResource
5
+ # class methods
6
+ extend Base::List
7
+ extend Base::Create
8
+ extend Base::Update
9
+
10
+ # instance methods
11
+ include Base::Save
12
+ include Base::Delete
13
+
14
+ # class constants
15
+ OBJECT_NAME = "checkout".freeze
16
+ RESOURCE_PATH = "checkouts".freeze
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module PrivacyGate
2
+ module APIResources
3
+ # Class that allows you to work with Event resource
4
+ class Event < Base::APIResource
5
+ # class methods
6
+ extend Base::List
7
+
8
+ # class constants
9
+ OBJECT_NAME = "event".freeze
10
+ RESOURCE_PATH = "events".freeze
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ module PrivacyGate
2
+ class PrivacyGateResponse
3
+ attr_accessor :data
4
+
5
+ attr_accessor :http_body
6
+
7
+ attr_accessor :http_headers
8
+
9
+ attr_accessor :http_status
10
+
11
+ attr_accessor :request_id
12
+
13
+ # Initializes a PrivacyGateResponse object
14
+ # from a Hash like the kind returned as part of a Faraday exception.
15
+ def self.from_faraday_hash(http_resp)
16
+ resp = PrivacyGateResponse.new
17
+ resp.data = JSON.parse(http_resp[:body], symbolize_names: true)
18
+ resp.http_body = http_resp[:body]
19
+ resp.http_headers = http_resp[:headers]
20
+ resp.http_status = http_resp[:status]
21
+ resp.request_id = http_resp[:headers]["x-request-id"]
22
+ resp
23
+ end
24
+
25
+ # Initializes a PrivacyGateResponse object
26
+ # from a Faraday HTTP response object.
27
+ def self.from_faraday_response(http_resp)
28
+ resp = PrivacyGateResponse.new
29
+ resp.data = JSON.parse(http_resp.body, symbolize_names: true)
30
+ resp.http_body = http_resp.body
31
+ resp.http_headers = http_resp.headers
32
+ resp.http_status = http_resp.status
33
+ resp.request_id = http_resp.headers["x-request-id"]
34
+
35
+ # unpack nested data field if it exist
36
+ if resp.data.is_a? Hash and resp.data.fetch(:data, nil).is_a? Hash
37
+ resp.data.update(resp.data.delete(:data))
38
+ end
39
+
40
+ # warn in there warnings in response
41
+ if resp.data.is_a? Hash and resp.data.fetch(:warnings, nil).is_a? Array
42
+ warn(resp.data[:warnings].first.to_s)
43
+ end
44
+
45
+ resp
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PrivacyGate
4
+ BASE_API_URL = "https://api.privacygate.io/"
5
+ API_VERSION = "2018-03-22"
6
+
7
+ class Client
8
+ # API Client for the PrivacyGate API.
9
+ # Entry point for making requests to the PrivacyGate API.
10
+ # Full API docs available here: https://api.privacygate.io/docs/
11
+
12
+ def initialize(options = {})
13
+ # set API key and API URL
14
+ check_api_key!(options[:api_key])
15
+ @api_key = options[:api_key]
16
+ @api_uri = URI.parse(options[:api_url] || BASE_API_URL)
17
+ @api_ver = options[:api_ver] || API_VERSION
18
+ # create client obj
19
+ @conn = Faraday.new do |c|
20
+ c.use Faraday::Request::Multipart
21
+ c.use Faraday::Request::UrlEncoded
22
+ c.use Faraday::Response::RaiseError
23
+ c.adapter Faraday.default_adapter
24
+ end
25
+ end
26
+
27
+ # Set client-resource relations with all API resources
28
+ # provide client instance to each resource
29
+ def charge
30
+ APIResources::Charge.client = self
31
+ APIResources::Charge
32
+ end
33
+
34
+ def checkout
35
+ APIResources::Checkout.client = self
36
+ APIResources::Checkout
37
+ end
38
+
39
+ def event
40
+ APIResources::Event.client = self
41
+ APIResources::Event
42
+ end
43
+
44
+ def api_url(url = "", api_base = nil)
45
+ (api_base || PrivacyGate::BASE_API_URL) + url
46
+ end
47
+
48
+ def request_headers(api_key)
49
+ {
50
+ "User-Agent" => "PrivacyGate/#{PrivacyGate::VERSION}",
51
+ "Accept" => "application/json",
52
+ "X-CC-Api-Key" => api_key,
53
+ "X-CC-Version" => @api_ver,
54
+ "Content-Type" => "application/json",
55
+ }
56
+ end
57
+
58
+ def check_api_key!(api_key)
59
+ raise AuthenticationError, "No API key provided" unless api_key
60
+ end
61
+
62
+ # request section
63
+ def request(method, path, params = {})
64
+ @last_response = nil
65
+ url = api_url(path, @api_uri)
66
+ headers = request_headers(@api_key)
67
+
68
+ body = nil
69
+ query_params = nil
70
+
71
+ case method.to_s.downcase.to_sym
72
+ when :get, :head, :delete
73
+ query_params = params
74
+ else
75
+ body = params.to_json
76
+ end
77
+
78
+ u = URI.parse(path)
79
+ unless u.query.nil?
80
+ query_params ||= {}
81
+ query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
82
+ end
83
+
84
+
85
+ http_resp = execute_request_with_rescues(@api_uri) do
86
+ @conn.run_request(method, url, body, headers) do |req|
87
+ req.params = query_params unless query_params.nil?
88
+ end
89
+ end
90
+
91
+ begin
92
+ resp = PrivacyGateResponse.from_faraday_response(http_resp)
93
+ rescue JSON::ParserError
94
+ raise Errors.general_api_error(http_resp.status, http_resp.body)
95
+ end
96
+
97
+ @last_response = resp
98
+ resp
99
+ end
100
+
101
+ # сollect errors during request execution if they occurred
102
+ def execute_request_with_rescues(api_base)
103
+ begin
104
+ resp = yield
105
+ rescue StandardError => e
106
+ case e
107
+ when Faraday::ClientError
108
+ if e.response
109
+ Errors.handle_error_response(e.response)
110
+ else
111
+ Errors.handle_network_error(e, api_base)
112
+ end
113
+ else
114
+ raise
115
+ end
116
+ end
117
+ resp
118
+ end
119
+ end
120
+ end