pingpp 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pingpp.rb ADDED
@@ -0,0 +1,274 @@
1
+ # Pingpp Ruby bindings
2
+ # API spec at https://pingplusplus.com/document/api
3
+ require 'cgi'
4
+ require 'set'
5
+ require 'openssl'
6
+ require 'rest_client'
7
+ require 'json'
8
+
9
+ # Version
10
+ require 'pingpp/version'
11
+
12
+ # Channel constants
13
+ require 'pingpp/channel'
14
+
15
+ # API operations
16
+ require 'pingpp/api_operations/create'
17
+ require 'pingpp/api_operations/update'
18
+ require 'pingpp/api_operations/delete'
19
+ require 'pingpp/api_operations/list'
20
+
21
+ # Resources
22
+ require 'pingpp/util'
23
+ require 'pingpp/pingpp_object'
24
+ require 'pingpp/api_resource'
25
+ require 'pingpp/singleton_api_resource'
26
+ require 'pingpp/list_object'
27
+ require 'pingpp/certificate_blacklist'
28
+ require 'pingpp/charge'
29
+ require 'pingpp/refund'
30
+
31
+ # Errors
32
+ require 'pingpp/errors/pingpp_error'
33
+ require 'pingpp/errors/api_error'
34
+ require 'pingpp/errors/api_connection_error'
35
+ require 'pingpp/errors/invalid_request_error'
36
+ require 'pingpp/errors/authentication_error'
37
+
38
+ module Pingpp
39
+ DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
40
+ @api_base = 'https://api.pingplusplus.com'
41
+
42
+ @api_version = '2014-10-10'
43
+
44
+ @ssl_bundle_path = DEFAULT_CA_BUNDLE_PATH
45
+ @verify_ssl_certs = true
46
+ @CERTIFICATE_VERIFIED = false
47
+
48
+
49
+ class << self
50
+ attr_accessor :api_key, :api_base, :verify_ssl_certs, :api_version
51
+ end
52
+
53
+ def self.api_url(url='')
54
+ @api_base + url
55
+ end
56
+
57
+ def self.request(method, url, api_key, params={}, headers={})
58
+ unless api_key ||= @api_key
59
+ raise AuthenticationError.new('No API key provided. ' +
60
+ 'Set your API key using "Pingpp.api_key = <API-KEY>". ' +
61
+ 'You can generate API keys from the Pingpp web interface. ' +
62
+ 'See https://pingplusplus.com/document/api for details, or email support@pingplusplus.com ' +
63
+ 'if you have any questions.')
64
+ end
65
+
66
+ if api_key =~ /\s/
67
+ raise AuthenticationError.new('Your API key is invalid, as it contains ' +
68
+ 'whitespace. (HINT: You can double-check your API key from the ' +
69
+ 'Pingpp web interface. See https://pingplusplus.com/document/api for details, or ' +
70
+ 'email support@pingplusplus.com if you have any questions.)')
71
+ end
72
+
73
+ request_opts = { :verify_ssl => false, :ssl_version => 'TLSv1' }
74
+
75
+ if ssl_preflight_passed?
76
+ request_opts.update(:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
77
+ :ssl_ca_file => @ssl_bundle_path)
78
+ end
79
+
80
+ if @verify_ssl_certs and !@CERTIFICATE_VERIFIED
81
+ @CERTIFICATE_VERIFIED = CertificateBlacklist.check_ssl_cert(@api_base, @ssl_bundle_path)
82
+ end
83
+
84
+ params = Util.objects_to_ids(params)
85
+ url = api_url(url)
86
+
87
+ case method.to_s.downcase.to_sym
88
+ when :get, :head, :delete
89
+ # Make params into GET parameters
90
+ url += "#{URI.parse(url).query ? '&' : '?'}#{uri_encode(params)}" if params && params.any?
91
+ payload = nil
92
+ else
93
+ payload = uri_encode(params)
94
+ end
95
+
96
+ request_opts.update(:headers => request_headers(api_key).update(headers),
97
+ :method => method, :open_timeout => 30,
98
+ :payload => payload, :url => url, :timeout => 80)
99
+
100
+ begin
101
+ response = execute_request(request_opts)
102
+ rescue SocketError => e
103
+ handle_restclient_error(e)
104
+ rescue NoMethodError => e
105
+ # Work around RestClient bug
106
+ if e.message =~ /\WRequestFailed\W/
107
+ e = APIConnectionError.new('Unexpected HTTP response code')
108
+ handle_restclient_error(e)
109
+ else
110
+ raise
111
+ end
112
+ rescue RestClient::ExceptionWithResponse => e
113
+ if rcode = e.http_code and rbody = e.http_body
114
+ handle_api_error(rcode, rbody)
115
+ else
116
+ handle_restclient_error(e)
117
+ end
118
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
119
+ handle_restclient_error(e)
120
+ end
121
+
122
+ [parse(response), api_key]
123
+ end
124
+
125
+ private
126
+
127
+ def self.ssl_preflight_passed?
128
+ if !verify_ssl_certs && !@no_verify
129
+ $stderr.puts "WARNING: Running without SSL cert verification. " +
130
+ "Execute 'Pingpp.verify_ssl_certs = true' to enable verification."
131
+
132
+ @no_verify = true
133
+
134
+ elsif !Util.file_readable(@ssl_bundle_path) && !@no_bundle
135
+ $stderr.puts "WARNING: Running without SSL cert verification " +
136
+ "because #{@ssl_bundle_path} isn't readable"
137
+
138
+ @no_bundle = true
139
+ end
140
+
141
+ !(@no_verify || @no_bundle)
142
+ end
143
+
144
+ def self.user_agent
145
+ @uname ||= get_uname
146
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
147
+
148
+ {
149
+ :bindings_version => Pingpp::VERSION,
150
+ :lang => 'ruby',
151
+ :lang_version => lang_version,
152
+ :platform => RUBY_PLATFORM,
153
+ :publisher => 'pingpp',
154
+ :uname => @uname
155
+ }
156
+
157
+ end
158
+
159
+ def self.get_uname
160
+ `uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
161
+ rescue Errno::ENOMEM => ex # couldn't create subprocess
162
+ "uname lookup failed"
163
+ end
164
+
165
+ def self.uri_encode(params)
166
+ Util.flatten_params(params).
167
+ map { |k,v| "#{k}=#{Util.url_encode(v)}" }.join('&')
168
+ end
169
+
170
+ def self.request_headers(api_key)
171
+ headers = {
172
+ :user_agent => "Pingpp/v1 RubyBindings/#{Pingpp::VERSION}",
173
+ :authorization => "Bearer #{api_key}",
174
+ :content_type => 'application/x-www-form-urlencoded'
175
+ }
176
+
177
+ headers[:pingplusplus_version] = api_version if api_version
178
+
179
+ begin
180
+ headers.update(:x_pingpp_client_user_agent => JSON.generate(user_agent))
181
+ rescue => e
182
+ headers.update(:x_pingpp_client_raw_user_agent => user_agent.inspect,
183
+ :error => "#{e} (#{e.class})")
184
+ end
185
+ end
186
+
187
+ def self.execute_request(opts)
188
+ RestClient::Request.execute(opts)
189
+ end
190
+
191
+ def self.parse(response)
192
+ begin
193
+ # Would use :symbolize_names => true, but apparently there is
194
+ # some library out there that makes symbolize_names not work.
195
+ response = JSON.parse(response.body)
196
+ rescue JSON::ParserError
197
+ raise general_api_error(response.code, response.body)
198
+ end
199
+
200
+ Util.symbolize_names(response)
201
+ end
202
+
203
+ def self.general_api_error(rcode, rbody)
204
+ APIError.new("Invalid response object from API: #{rbody.inspect} " +
205
+ "(HTTP response code was #{rcode})", rcode, rbody)
206
+ end
207
+
208
+ def self.handle_api_error(rcode, rbody)
209
+ begin
210
+ error_obj = JSON.parse(rbody)
211
+ error_obj = Util.symbolize_names(error_obj)
212
+ error = error_obj[:error] or raise PingppError.new # escape from parsing
213
+
214
+ rescue JSON::ParserError, PingppError
215
+ raise general_api_error(rcode, rbody)
216
+ end
217
+
218
+ case rcode
219
+ when 400, 404
220
+ raise invalid_request_error error, rcode, rbody, error_obj
221
+ when 401
222
+ raise authentication_error error, rcode, rbody, error_obj
223
+ else
224
+ raise api_error error, rcode, rbody, error_obj
225
+ end
226
+
227
+ end
228
+
229
+ def self.invalid_request_error(error, rcode, rbody, error_obj)
230
+ InvalidRequestError.new(error[:message], error[:param], rcode,
231
+ rbody, error_obj)
232
+ end
233
+
234
+ def self.authentication_error(error, rcode, rbody, error_obj)
235
+ AuthenticationError.new(error[:message], rcode, rbody, error_obj)
236
+ end
237
+
238
+ def self.api_error(error, rcode, rbody, error_obj)
239
+ APIError.new(error[:message], rcode, rbody, error_obj)
240
+ end
241
+
242
+ def self.handle_restclient_error(e)
243
+ connection_message = "Please check your internet connection and try again. " \
244
+ "If this problem persists, you should check Pingpp's service status at " \
245
+ "https://pingplusplus.com, or let us know at support@pingplusplus.com."
246
+
247
+ case e
248
+ when RestClient::RequestTimeout
249
+ message = "Could not connect to Pingpp (#{@api_base}). #{connection_message}"
250
+
251
+ when RestClient::ServerBrokeConnection
252
+ message = "The connection to the server (#{@api_base}) broke before the " \
253
+ "request completed. #{connection_message}"
254
+
255
+ when RestClient::SSLCertificateNotVerified
256
+ message = "Could not verify Pingpp's SSL certificate. " \
257
+ "Please make sure that your network is not intercepting certificates. " \
258
+ "(Try going to (#{@api_base}) in your browser.) " \
259
+ "If this problem persists, let us know at support@pingplusplus.com."
260
+
261
+ when SocketError
262
+ message = "Unexpected error communicating when trying to connect to Pingpp. " \
263
+ "You may be seeing this message because your DNS is not working. " \
264
+ "To check, try running 'host pingplusplus.com' from the command line."
265
+
266
+ else
267
+ message = "Unexpected error communicating with Pingpp. " \
268
+ "If this problem persists, let us know at support@pingplusplus.com."
269
+
270
+ end
271
+
272
+ raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
273
+ end
274
+ end
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pingpp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Xufeng Weng
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mime-types
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.25'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '3.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '1.25'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: json
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.8'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 1.8.1
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '1.8'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 1.8.1
67
+ - !ruby/object:Gem::Dependency
68
+ name: mocha
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: 0.13.2
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: 0.13.2
81
+ - !ruby/object:Gem::Dependency
82
+ name: shoulda
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '3.4'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 3.4.0
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.4'
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 3.4.0
101
+ - !ruby/object:Gem::Dependency
102
+ name: test-unit
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ - !ruby/object:Gem::Dependency
116
+ name: rake
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ description: PingPlusPlus is the easiest way to accept payments online. See https://pingplusplus.com
130
+ for details.
131
+ email:
132
+ - xufeng.weng@pingplusplus.com
133
+ executables: []
134
+ extensions: []
135
+ extra_rdoc_files: []
136
+ files:
137
+ - lib/data/ca-certificates.crt
138
+ - lib/pingpp.rb
139
+ - lib/pingpp/api_operations/create.rb
140
+ - lib/pingpp/api_operations/delete.rb
141
+ - lib/pingpp/api_operations/list.rb
142
+ - lib/pingpp/api_operations/update.rb
143
+ - lib/pingpp/api_resource.rb
144
+ - lib/pingpp/certificate_blacklist.rb
145
+ - lib/pingpp/channel.rb
146
+ - lib/pingpp/charge.rb
147
+ - lib/pingpp/errors/api_connection_error.rb
148
+ - lib/pingpp/errors/api_error.rb
149
+ - lib/pingpp/errors/authentication_error.rb
150
+ - lib/pingpp/errors/invalid_request_error.rb
151
+ - lib/pingpp/errors/pingpp_error.rb
152
+ - lib/pingpp/list_object.rb
153
+ - lib/pingpp/pingpp_object.rb
154
+ - lib/pingpp/refund.rb
155
+ - lib/pingpp/singleton_api_resource.rb
156
+ - lib/pingpp/util.rb
157
+ - lib/pingpp/version.rb
158
+ homepage: https://pingplusplus.com/document/api
159
+ licenses:
160
+ - MIT
161
+ metadata: {}
162
+ post_install_message:
163
+ rdoc_options: []
164
+ require_paths:
165
+ - lib
166
+ required_ruby_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ requirements: []
177
+ rubyforge_project:
178
+ rubygems_version: 2.4.2
179
+ signing_key:
180
+ specification_version: 4
181
+ summary: Ruby bindings for the PingPlusPlus API
182
+ test_files: []