pingpp 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.
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: []